xref: /vim-8.2.3635/src/textobject.c (revision 2d2970ea)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * textobject.c: functions for text objects
12  */
13 #include "vim.h"
14 
15 static int cls(void);
16 static int skip_chars(int, int);
17 
18 /*
19  * Find the start of the next sentence, searching in the direction specified
20  * by the "dir" argument.  The cursor is positioned on the start of the next
21  * sentence when found.  If the next sentence is found, return OK.  Return FAIL
22  * otherwise.  See ":h sentence" for the precise definition of a "sentence"
23  * text object.
24  */
25     int
26 findsent(int dir, long count)
27 {
28     pos_T	pos, tpos;
29     int		c;
30     int		(*func)(pos_T *);
31     int		startlnum;
32     int		noskip = FALSE;	    // do not skip blanks
33     int		cpo_J;
34     int		found_dot;
35 
36     pos = curwin->w_cursor;
37     if (dir == FORWARD)
38 	func = incl;
39     else
40 	func = decl;
41 
42     while (count--)
43     {
44 	/*
45 	 * if on an empty line, skip up to a non-empty line
46 	 */
47 	if (gchar_pos(&pos) == NUL)
48 	{
49 	    do
50 		if ((*func)(&pos) == -1)
51 		    break;
52 	    while (gchar_pos(&pos) == NUL);
53 	    if (dir == FORWARD)
54 		goto found;
55 	}
56 	/*
57 	 * if on the start of a paragraph or a section and searching forward,
58 	 * go to the next line
59 	 */
60 	else if (dir == FORWARD && pos.col == 0 &&
61 						startPS(pos.lnum, NUL, FALSE))
62 	{
63 	    if (pos.lnum == curbuf->b_ml.ml_line_count)
64 		return FAIL;
65 	    ++pos.lnum;
66 	    goto found;
67 	}
68 	else if (dir == BACKWARD)
69 	    decl(&pos);
70 
71 	// go back to the previous non-white non-punctuation character
72 	found_dot = FALSE;
73 	while (c = gchar_pos(&pos), VIM_ISWHITE(c)
74 				|| vim_strchr((char_u *)".!?)]\"'", c) != NULL)
75 	{
76 	    tpos = pos;
77 	    if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD))
78 		break;
79 
80 	    if (found_dot)
81 		break;
82 	    if (vim_strchr((char_u *) ".!?", c) != NULL)
83 		found_dot = TRUE;
84 
85 	    if (vim_strchr((char_u *) ")]\"'", c) != NULL
86 		&& vim_strchr((char_u *) ".!?)]\"'", gchar_pos(&tpos)) == NULL)
87 		break;
88 
89 	    decl(&pos);
90 	}
91 
92 	// remember the line where the search started
93 	startlnum = pos.lnum;
94 	cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL;
95 
96 	for (;;)		// find end of sentence
97 	{
98 	    c = gchar_pos(&pos);
99 	    if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE)))
100 	    {
101 		if (dir == BACKWARD && pos.lnum != startlnum)
102 		    ++pos.lnum;
103 		break;
104 	    }
105 	    if (c == '.' || c == '!' || c == '?')
106 	    {
107 		tpos = pos;
108 		do
109 		    if ((c = inc(&tpos)) == -1)
110 			break;
111 		while (vim_strchr((char_u *)")]\"'", c = gchar_pos(&tpos))
112 			!= NULL);
113 		if (c == -1  || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL
114 		    || (cpo_J && (c == ' ' && inc(&tpos) >= 0
115 			      && gchar_pos(&tpos) == ' ')))
116 		{
117 		    pos = tpos;
118 		    if (gchar_pos(&pos) == NUL) // skip NUL at EOL
119 			inc(&pos);
120 		    break;
121 		}
122 	    }
123 	    if ((*func)(&pos) == -1)
124 	    {
125 		if (count)
126 		    return FAIL;
127 		noskip = TRUE;
128 		break;
129 	    }
130 	}
131 found:
132 	    // skip white space
133 	while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t'))
134 	    if (incl(&pos) == -1)
135 		break;
136     }
137 
138     setpcmark();
139     curwin->w_cursor = pos;
140     return OK;
141 }
142 
143 /*
144  * Find the next paragraph or section in direction 'dir'.
145  * Paragraphs are currently supposed to be separated by empty lines.
146  * If 'what' is NUL we go to the next paragraph.
147  * If 'what' is '{' or '}' we go to the next section.
148  * If 'both' is TRUE also stop at '}'.
149  * Return TRUE if the next paragraph or section was found.
150  */
151     int
152 findpar(
153     int		*pincl,	    // Return: TRUE if last char is to be included
154     int		dir,
155     long	count,
156     int		what,
157     int		both)
158 {
159     linenr_T	curr;
160     int		did_skip;   // TRUE after separating lines have been skipped
161     int		first;	    // TRUE on first line
162     int		posix = (vim_strchr(p_cpo, CPO_PARA) != NULL);
163 #ifdef FEAT_FOLDING
164     linenr_T	fold_first;	// first line of a closed fold
165     linenr_T	fold_last;	// last line of a closed fold
166     int		fold_skipped;	// TRUE if a closed fold was skipped this
167 				// iteration
168 #endif
169 
170     curr = curwin->w_cursor.lnum;
171 
172     while (count--)
173     {
174 	did_skip = FALSE;
175 	for (first = TRUE; ; first = FALSE)
176 	{
177 	    if (*ml_get(curr) != NUL)
178 		did_skip = TRUE;
179 
180 #ifdef FEAT_FOLDING
181 	    // skip folded lines
182 	    fold_skipped = FALSE;
183 	    if (first && hasFolding(curr, &fold_first, &fold_last))
184 	    {
185 		curr = ((dir > 0) ? fold_last : fold_first) + dir;
186 		fold_skipped = TRUE;
187 	    }
188 #endif
189 
190 	    // POSIX has its own ideas of what a paragraph boundary is and it
191 	    // doesn't match historical Vi: It also stops at a "{" in the
192 	    // first column and at an empty line.
193 	    if (!first && did_skip && (startPS(curr, what, both)
194 			   || (posix && what == NUL && *ml_get(curr) == '{')))
195 		break;
196 
197 #ifdef FEAT_FOLDING
198 	    if (fold_skipped)
199 		curr -= dir;
200 #endif
201 	    if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count)
202 	    {
203 		if (count)
204 		    return FALSE;
205 		curr -= dir;
206 		break;
207 	    }
208 	}
209     }
210     setpcmark();
211     if (both && *ml_get(curr) == '}')	// include line with '}'
212 	++curr;
213     curwin->w_cursor.lnum = curr;
214     if (curr == curbuf->b_ml.ml_line_count && what != '}')
215     {
216 	char_u *line = ml_get(curr);
217 
218 	// Put the cursor on the last character in the last line and make the
219 	// motion inclusive.
220 	if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0)
221 	{
222 	    --curwin->w_cursor.col;
223 	    curwin->w_cursor.col -=
224 			     (*mb_head_off)(line, line + curwin->w_cursor.col);
225 	    *pincl = TRUE;
226 	}
227     }
228     else
229 	curwin->w_cursor.col = 0;
230     return TRUE;
231 }
232 
233 /*
234  * check if the string 's' is a nroff macro that is in option 'opt'
235  */
236     static int
237 inmacro(char_u *opt, char_u *s)
238 {
239     char_u	*macro;
240 
241     for (macro = opt; macro[0]; ++macro)
242     {
243 	// Accept two characters in the option being equal to two characters
244 	// in the line.  A space in the option matches with a space in the
245 	// line or the line having ended.
246 	if (       (macro[0] == s[0]
247 		    || (macro[0] == ' '
248 			&& (s[0] == NUL || s[0] == ' ')))
249 		&& (macro[1] == s[1]
250 		    || ((macro[1] == NUL || macro[1] == ' ')
251 			&& (s[0] == NUL || s[1] == NUL || s[1] == ' '))))
252 	    break;
253 	++macro;
254 	if (macro[0] == NUL)
255 	    break;
256     }
257     return (macro[0] != NUL);
258 }
259 
260 /*
261  * startPS: return TRUE if line 'lnum' is the start of a section or paragraph.
262  * If 'para' is '{' or '}' only check for sections.
263  * If 'both' is TRUE also stop at '}'
264  */
265     int
266 startPS(linenr_T lnum, int para, int both)
267 {
268     char_u	*s;
269 
270     s = ml_get(lnum);
271     if (*s == para || *s == '\f' || (both && *s == '}'))
272 	return TRUE;
273     if (*s == '.' && (inmacro(p_sections, s + 1) ||
274 					   (!para && inmacro(p_para, s + 1))))
275 	return TRUE;
276     return FALSE;
277 }
278 
279 /*
280  * The following routines do the word searches performed by the 'w', 'W',
281  * 'b', 'B', 'e', and 'E' commands.
282  */
283 
284 /*
285  * To perform these searches, characters are placed into one of three
286  * classes, and transitions between classes determine word boundaries.
287  *
288  * The classes are:
289  *
290  * 0 - white space
291  * 1 - punctuation
292  * 2 or higher - keyword characters (letters, digits and underscore)
293  */
294 
295 static int	cls_bigword;	// TRUE for "W", "B" or "E"
296 
297 /*
298  * cls() - returns the class of character at curwin->w_cursor
299  *
300  * If a 'W', 'B', or 'E' motion is being done (cls_bigword == TRUE), chars
301  * from class 2 and higher are reported as class 1 since only white space
302  * boundaries are of interest.
303  */
304     static int
305 cls(void)
306 {
307     int	    c;
308 
309     c = gchar_cursor();
310     if (c == ' ' || c == '\t' || c == NUL)
311 	return 0;
312     if (enc_dbcs != 0 && c > 0xFF)
313     {
314 	// If cls_bigword, report multi-byte chars as class 1.
315 	if (enc_dbcs == DBCS_KOR && cls_bigword)
316 	    return 1;
317 
318 	// process code leading/trailing bytes
319 	return dbcs_class(((unsigned)c >> 8), (c & 0xFF));
320     }
321     if (enc_utf8)
322     {
323 	c = utf_class(c);
324 	if (c != 0 && cls_bigword)
325 	    return 1;
326 	return c;
327     }
328 
329     // If cls_bigword is TRUE, report all non-blanks as class 1.
330     if (cls_bigword)
331 	return 1;
332 
333     if (vim_iswordc(c))
334 	return 2;
335     return 1;
336 }
337 
338 
339 /*
340  * fwd_word(count, type, eol) - move forward one word
341  *
342  * Returns FAIL if the cursor was already at the end of the file.
343  * If eol is TRUE, last word stops at end of line (for operators).
344  */
345     int
346 fwd_word(
347     long	count,
348     int		bigword,    // "W", "E" or "B"
349     int		eol)
350 {
351     int		sclass;	    // starting class
352     int		i;
353     int		last_line;
354 
355     curwin->w_cursor.coladd = 0;
356     cls_bigword = bigword;
357     while (--count >= 0)
358     {
359 #ifdef FEAT_FOLDING
360 	// When inside a range of folded lines, move to the last char of the
361 	// last line.
362 	if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum))
363 	    coladvance((colnr_T)MAXCOL);
364 #endif
365 	sclass = cls();
366 
367 	/*
368 	 * We always move at least one character, unless on the last
369 	 * character in the buffer.
370 	 */
371 	last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count);
372 	i = inc_cursor();
373 	if (i == -1 || (i >= 1 && last_line)) // started at last char in file
374 	    return FAIL;
375 	if (i >= 1 && eol && count == 0)      // started at last char in line
376 	    return OK;
377 
378 	/*
379 	 * Go one char past end of current word (if any)
380 	 */
381 	if (sclass != 0)
382 	    while (cls() == sclass)
383 	    {
384 		i = inc_cursor();
385 		if (i == -1 || (i >= 1 && eol && count == 0))
386 		    return OK;
387 	    }
388 
389 	/*
390 	 * go to next non-white
391 	 */
392 	while (cls() == 0)
393 	{
394 	    /*
395 	     * We'll stop if we land on a blank line
396 	     */
397 	    if (curwin->w_cursor.col == 0 && *ml_get_curline() == NUL)
398 		break;
399 
400 	    i = inc_cursor();
401 	    if (i == -1 || (i >= 1 && eol && count == 0))
402 		return OK;
403 	}
404     }
405     return OK;
406 }
407 
408 /*
409  * bck_word() - move backward 'count' words
410  *
411  * If stop is TRUE and we are already on the start of a word, move one less.
412  *
413  * Returns FAIL if top of the file was reached.
414  */
415     int
416 bck_word(long count, int bigword, int stop)
417 {
418     int		sclass;	    // starting class
419 
420     curwin->w_cursor.coladd = 0;
421     cls_bigword = bigword;
422     while (--count >= 0)
423     {
424 #ifdef FEAT_FOLDING
425 	// When inside a range of folded lines, move to the first char of the
426 	// first line.
427 	if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL))
428 	    curwin->w_cursor.col = 0;
429 #endif
430 	sclass = cls();
431 	if (dec_cursor() == -1)		// started at start of file
432 	    return FAIL;
433 
434 	if (!stop || sclass == cls() || sclass == 0)
435 	{
436 	    /*
437 	     * Skip white space before the word.
438 	     * Stop on an empty line.
439 	     */
440 	    while (cls() == 0)
441 	    {
442 		if (curwin->w_cursor.col == 0
443 				      && LINEEMPTY(curwin->w_cursor.lnum))
444 		    goto finished;
445 		if (dec_cursor() == -1) // hit start of file, stop here
446 		    return OK;
447 	    }
448 
449 	    /*
450 	     * Move backward to start of this word.
451 	     */
452 	    if (skip_chars(cls(), BACKWARD))
453 		return OK;
454 	}
455 
456 	inc_cursor();			// overshot - forward one
457 finished:
458 	stop = FALSE;
459     }
460     return OK;
461 }
462 
463 /*
464  * end_word() - move to the end of the word
465  *
466  * There is an apparent bug in the 'e' motion of the real vi. At least on the
467  * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e'
468  * motion crosses blank lines. When the real vi crosses a blank line in an
469  * 'e' motion, the cursor is placed on the FIRST character of the next
470  * non-blank line. The 'E' command, however, works correctly. Since this
471  * appears to be a bug, I have not duplicated it here.
472  *
473  * Returns FAIL if end of the file was reached.
474  *
475  * If stop is TRUE and we are already on the end of a word, move one less.
476  * If empty is TRUE stop on an empty line.
477  */
478     int
479 end_word(
480     long	count,
481     int		bigword,
482     int		stop,
483     int		empty)
484 {
485     int		sclass;	    // starting class
486 
487     curwin->w_cursor.coladd = 0;
488     cls_bigword = bigword;
489     while (--count >= 0)
490     {
491 #ifdef FEAT_FOLDING
492 	// When inside a range of folded lines, move to the last char of the
493 	// last line.
494 	if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum))
495 	    coladvance((colnr_T)MAXCOL);
496 #endif
497 	sclass = cls();
498 	if (inc_cursor() == -1)
499 	    return FAIL;
500 
501 	/*
502 	 * If we're in the middle of a word, we just have to move to the end
503 	 * of it.
504 	 */
505 	if (cls() == sclass && sclass != 0)
506 	{
507 	    /*
508 	     * Move forward to end of the current word
509 	     */
510 	    if (skip_chars(sclass, FORWARD))
511 		return FAIL;
512 	}
513 	else if (!stop || sclass == 0)
514 	{
515 	    /*
516 	     * We were at the end of a word. Go to the end of the next word.
517 	     * First skip white space, if 'empty' is TRUE, stop at empty line.
518 	     */
519 	    while (cls() == 0)
520 	    {
521 		if (empty && curwin->w_cursor.col == 0
522 					  && LINEEMPTY(curwin->w_cursor.lnum))
523 		    goto finished;
524 		if (inc_cursor() == -1)	    // hit end of file, stop here
525 		    return FAIL;
526 	    }
527 
528 	    /*
529 	     * Move forward to the end of this word.
530 	     */
531 	    if (skip_chars(cls(), FORWARD))
532 		return FAIL;
533 	}
534 	dec_cursor();			// overshot - one char backward
535 finished:
536 	stop = FALSE;			// we move only one word less
537     }
538     return OK;
539 }
540 
541 /*
542  * Move back to the end of the word.
543  *
544  * Returns FAIL if start of the file was reached.
545  */
546     int
547 bckend_word(
548     long	count,
549     int		bigword,    // TRUE for "B"
550     int		eol)	    // TRUE: stop at end of line.
551 {
552     int		sclass;	    // starting class
553     int		i;
554 
555     curwin->w_cursor.coladd = 0;
556     cls_bigword = bigword;
557     while (--count >= 0)
558     {
559 	sclass = cls();
560 	if ((i = dec_cursor()) == -1)
561 	    return FAIL;
562 	if (eol && i == 1)
563 	    return OK;
564 
565 	/*
566 	 * Move backward to before the start of this word.
567 	 */
568 	if (sclass != 0)
569 	{
570 	    while (cls() == sclass)
571 		if ((i = dec_cursor()) == -1 || (eol && i == 1))
572 		    return OK;
573 	}
574 
575 	/*
576 	 * Move backward to end of the previous word
577 	 */
578 	while (cls() == 0)
579 	{
580 	    if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum))
581 		break;
582 	    if ((i = dec_cursor()) == -1 || (eol && i == 1))
583 		return OK;
584 	}
585     }
586     return OK;
587 }
588 
589 /*
590  * Skip a row of characters of the same class.
591  * Return TRUE when end-of-file reached, FALSE otherwise.
592  */
593     static int
594 skip_chars(int cclass, int dir)
595 {
596     while (cls() == cclass)
597 	if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1)
598 	    return TRUE;
599     return FALSE;
600 }
601 
602 #if defined(FEAT_TEXTOBJ) || defined(PROTO)
603 /*
604  * Go back to the start of the word or the start of white space
605  */
606     static void
607 back_in_line(void)
608 {
609     int		sclass;		    // starting class
610 
611     sclass = cls();
612     for (;;)
613     {
614 	if (curwin->w_cursor.col == 0)	    // stop at start of line
615 	    break;
616 	dec_cursor();
617 	if (cls() != sclass)		    // stop at start of word
618 	{
619 	    inc_cursor();
620 	    break;
621 	}
622     }
623 }
624 
625     static void
626 find_first_blank(pos_T *posp)
627 {
628     int	    c;
629 
630     while (decl(posp) != -1)
631     {
632 	c = gchar_pos(posp);
633 	if (!VIM_ISWHITE(c))
634 	{
635 	    incl(posp);
636 	    break;
637 	}
638     }
639 }
640 
641 /*
642  * Skip count/2 sentences and count/2 separating white spaces.
643  */
644     static void
645 findsent_forward(
646     long    count,
647     int	    at_start_sent)	// cursor is at start of sentence
648 {
649     while (count--)
650     {
651 	findsent(FORWARD, 1L);
652 	if (at_start_sent)
653 	    find_first_blank(&curwin->w_cursor);
654 	if (count == 0 || at_start_sent)
655 	    decl(&curwin->w_cursor);
656 	at_start_sent = !at_start_sent;
657     }
658 }
659 
660 /*
661  * Find word under cursor, cursor at end.
662  * Used while an operator is pending, and in Visual mode.
663  */
664     int
665 current_word(
666     oparg_T	*oap,
667     long	count,
668     int		include,	// TRUE: include word and white space
669     int		bigword)	// FALSE == word, TRUE == WORD
670 {
671     pos_T	start_pos;
672     pos_T	pos;
673     int		inclusive = TRUE;
674     int		include_white = FALSE;
675 
676     cls_bigword = bigword;
677     CLEAR_POS(&start_pos);
678 
679     // Correct cursor when 'selection' is exclusive
680     if (VIsual_active && *p_sel == 'e' && LT_POS(VIsual, curwin->w_cursor))
681 	dec_cursor();
682 
683     /*
684      * When Visual mode is not active, or when the VIsual area is only one
685      * character, select the word and/or white space under the cursor.
686      */
687     if (!VIsual_active || EQUAL_POS(curwin->w_cursor, VIsual))
688     {
689 	/*
690 	 * Go to start of current word or white space.
691 	 */
692 	back_in_line();
693 	start_pos = curwin->w_cursor;
694 
695 	/*
696 	 * If the start is on white space, and white space should be included
697 	 * ("	word"), or start is not on white space, and white space should
698 	 * not be included ("word"), find end of word.
699 	 */
700 	if ((cls() == 0) == include)
701 	{
702 	    if (end_word(1L, bigword, TRUE, TRUE) == FAIL)
703 		return FAIL;
704 	}
705 	else
706 	{
707 	    /*
708 	     * If the start is not on white space, and white space should be
709 	     * included ("word	 "), or start is on white space and white
710 	     * space should not be included ("	 "), find start of word.
711 	     * If we end up in the first column of the next line (single char
712 	     * word) back up to end of the line.
713 	     */
714 	    fwd_word(1L, bigword, TRUE);
715 	    if (curwin->w_cursor.col == 0)
716 		decl(&curwin->w_cursor);
717 	    else
718 		oneleft();
719 
720 	    if (include)
721 		include_white = TRUE;
722 	}
723 
724 	if (VIsual_active)
725 	{
726 	    // should do something when inclusive == FALSE !
727 	    VIsual = start_pos;
728 	    redraw_curbuf_later(INVERTED);	// update the inversion
729 	}
730 	else
731 	{
732 	    oap->start = start_pos;
733 	    oap->motion_type = MCHAR;
734 	}
735 	--count;
736     }
737 
738     /*
739      * When count is still > 0, extend with more objects.
740      */
741     while (count > 0)
742     {
743 	inclusive = TRUE;
744 	if (VIsual_active && LT_POS(curwin->w_cursor, VIsual))
745 	{
746 	    /*
747 	     * In Visual mode, with cursor at start: move cursor back.
748 	     */
749 	    if (decl(&curwin->w_cursor) == -1)
750 		return FAIL;
751 	    if (include != (cls() != 0))
752 	    {
753 		if (bck_word(1L, bigword, TRUE) == FAIL)
754 		    return FAIL;
755 	    }
756 	    else
757 	    {
758 		if (bckend_word(1L, bigword, TRUE) == FAIL)
759 		    return FAIL;
760 		(void)incl(&curwin->w_cursor);
761 	    }
762 	}
763 	else
764 	{
765 	    /*
766 	     * Move cursor forward one word and/or white area.
767 	     */
768 	    if (incl(&curwin->w_cursor) == -1)
769 		return FAIL;
770 	    if (include != (cls() == 0))
771 	    {
772 		if (fwd_word(1L, bigword, TRUE) == FAIL && count > 1)
773 		    return FAIL;
774 		/*
775 		 * If end is just past a new-line, we don't want to include
776 		 * the first character on the line.
777 		 * Put cursor on last char of white.
778 		 */
779 		if (oneleft() == FAIL)
780 		    inclusive = FALSE;
781 	    }
782 	    else
783 	    {
784 		if (end_word(1L, bigword, TRUE, TRUE) == FAIL)
785 		    return FAIL;
786 	    }
787 	}
788 	--count;
789     }
790 
791     if (include_white && (cls() != 0
792 		 || (curwin->w_cursor.col == 0 && !inclusive)))
793     {
794 	/*
795 	 * If we don't include white space at the end, move the start
796 	 * to include some white space there. This makes "daw" work
797 	 * better on the last word in a sentence (and "2daw" on last-but-one
798 	 * word).  Also when "2daw" deletes "word." at the end of the line
799 	 * (cursor is at start of next line).
800 	 * But don't delete white space at start of line (indent).
801 	 */
802 	pos = curwin->w_cursor;	// save cursor position
803 	curwin->w_cursor = start_pos;
804 	if (oneleft() == OK)
805 	{
806 	    back_in_line();
807 	    if (cls() == 0 && curwin->w_cursor.col > 0)
808 	    {
809 		if (VIsual_active)
810 		    VIsual = curwin->w_cursor;
811 		else
812 		    oap->start = curwin->w_cursor;
813 	    }
814 	}
815 	curwin->w_cursor = pos;	// put cursor back at end
816     }
817 
818     if (VIsual_active)
819     {
820 	if (*p_sel == 'e' && inclusive && LTOREQ_POS(VIsual, curwin->w_cursor))
821 	    inc_cursor();
822 	if (VIsual_mode == 'V')
823 	{
824 	    VIsual_mode = 'v';
825 	    redraw_cmdline = TRUE;		// show mode later
826 	}
827     }
828     else
829 	oap->inclusive = inclusive;
830 
831     return OK;
832 }
833 
834 /*
835  * Find sentence(s) under the cursor, cursor at end.
836  * When Visual active, extend it by one or more sentences.
837  */
838     int
839 current_sent(oparg_T *oap, long count, int include)
840 {
841     pos_T	start_pos;
842     pos_T	pos;
843     int		start_blank;
844     int		c;
845     int		at_start_sent;
846     long	ncount;
847 
848     start_pos = curwin->w_cursor;
849     pos = start_pos;
850     findsent(FORWARD, 1L);	// Find start of next sentence.
851 
852     /*
853      * When the Visual area is bigger than one character: Extend it.
854      */
855     if (VIsual_active && !EQUAL_POS(start_pos, VIsual))
856     {
857 extend:
858 	if (LT_POS(start_pos, VIsual))
859 	{
860 	    /*
861 	     * Cursor at start of Visual area.
862 	     * Find out where we are:
863 	     * - in the white space before a sentence
864 	     * - in a sentence or just after it
865 	     * - at the start of a sentence
866 	     */
867 	    at_start_sent = TRUE;
868 	    decl(&pos);
869 	    while (LT_POS(pos, curwin->w_cursor))
870 	    {
871 		c = gchar_pos(&pos);
872 		if (!VIM_ISWHITE(c))
873 		{
874 		    at_start_sent = FALSE;
875 		    break;
876 		}
877 		incl(&pos);
878 	    }
879 	    if (!at_start_sent)
880 	    {
881 		findsent(BACKWARD, 1L);
882 		if (EQUAL_POS(curwin->w_cursor, start_pos))
883 		    at_start_sent = TRUE;  // exactly at start of sentence
884 		else
885 		    // inside a sentence, go to its end (start of next)
886 		    findsent(FORWARD, 1L);
887 	    }
888 	    if (include)	// "as" gets twice as much as "is"
889 		count *= 2;
890 	    while (count--)
891 	    {
892 		if (at_start_sent)
893 		    find_first_blank(&curwin->w_cursor);
894 		c = gchar_cursor();
895 		if (!at_start_sent || (!include && !VIM_ISWHITE(c)))
896 		    findsent(BACKWARD, 1L);
897 		at_start_sent = !at_start_sent;
898 	    }
899 	}
900 	else
901 	{
902 	    /*
903 	     * Cursor at end of Visual area.
904 	     * Find out where we are:
905 	     * - just before a sentence
906 	     * - just before or in the white space before a sentence
907 	     * - in a sentence
908 	     */
909 	    incl(&pos);
910 	    at_start_sent = TRUE;
911 	    // not just before a sentence
912 	    if (!EQUAL_POS(pos, curwin->w_cursor))
913 	    {
914 		at_start_sent = FALSE;
915 		while (LT_POS(pos, curwin->w_cursor))
916 		{
917 		    c = gchar_pos(&pos);
918 		    if (!VIM_ISWHITE(c))
919 		    {
920 			at_start_sent = TRUE;
921 			break;
922 		    }
923 		    incl(&pos);
924 		}
925 		if (at_start_sent)	// in the sentence
926 		    findsent(BACKWARD, 1L);
927 		else		// in/before white before a sentence
928 		    curwin->w_cursor = start_pos;
929 	    }
930 
931 	    if (include)	// "as" gets twice as much as "is"
932 		count *= 2;
933 	    findsent_forward(count, at_start_sent);
934 	    if (*p_sel == 'e')
935 		++curwin->w_cursor.col;
936 	}
937 	return OK;
938     }
939 
940     /*
941      * If the cursor started on a blank, check if it is just before the start
942      * of the next sentence.
943      */
944     while (c = gchar_pos(&pos), VIM_ISWHITE(c))	// VIM_ISWHITE() is a macro
945 	incl(&pos);
946     if (EQUAL_POS(pos, curwin->w_cursor))
947     {
948 	start_blank = TRUE;
949 	find_first_blank(&start_pos);	// go back to first blank
950     }
951     else
952     {
953 	start_blank = FALSE;
954 	findsent(BACKWARD, 1L);
955 	start_pos = curwin->w_cursor;
956     }
957     if (include)
958 	ncount = count * 2;
959     else
960     {
961 	ncount = count;
962 	if (start_blank)
963 	    --ncount;
964     }
965     if (ncount > 0)
966 	findsent_forward(ncount, TRUE);
967     else
968 	decl(&curwin->w_cursor);
969 
970     if (include)
971     {
972 	/*
973 	 * If the blank in front of the sentence is included, exclude the
974 	 * blanks at the end of the sentence, go back to the first blank.
975 	 * If there are no trailing blanks, try to include leading blanks.
976 	 */
977 	if (start_blank)
978 	{
979 	    find_first_blank(&curwin->w_cursor);
980 	    c = gchar_pos(&curwin->w_cursor);	// VIM_ISWHITE() is a macro
981 	    if (VIM_ISWHITE(c))
982 		decl(&curwin->w_cursor);
983 	}
984 	else if (c = gchar_cursor(), !VIM_ISWHITE(c))
985 	    find_first_blank(&start_pos);
986     }
987 
988     if (VIsual_active)
989     {
990 	// Avoid getting stuck with "is" on a single space before a sentence.
991 	if (EQUAL_POS(start_pos, curwin->w_cursor))
992 	    goto extend;
993 	if (*p_sel == 'e')
994 	    ++curwin->w_cursor.col;
995 	VIsual = start_pos;
996 	VIsual_mode = 'v';
997 	redraw_cmdline = TRUE;		// show mode later
998 	redraw_curbuf_later(INVERTED);	// update the inversion
999     }
1000     else
1001     {
1002 	// include a newline after the sentence, if there is one
1003 	if (incl(&curwin->w_cursor) == -1)
1004 	    oap->inclusive = TRUE;
1005 	else
1006 	    oap->inclusive = FALSE;
1007 	oap->start = start_pos;
1008 	oap->motion_type = MCHAR;
1009     }
1010     return OK;
1011 }
1012 
1013 /*
1014  * Find block under the cursor, cursor at end.
1015  * "what" and "other" are two matching parenthesis/brace/etc.
1016  */
1017     int
1018 current_block(
1019     oparg_T	*oap,
1020     long	count,
1021     int		include,	// TRUE == include white space
1022     int		what,		// '(', '{', etc.
1023     int		other)		// ')', '}', etc.
1024 {
1025     pos_T	old_pos;
1026     pos_T	*pos = NULL;
1027     pos_T	start_pos;
1028     pos_T	*end_pos;
1029     pos_T	old_start, old_end;
1030     char_u	*save_cpo;
1031     int		sol = FALSE;		// '{' at start of line
1032 
1033     old_pos = curwin->w_cursor;
1034     old_end = curwin->w_cursor;		// remember where we started
1035     old_start = old_end;
1036 
1037     /*
1038      * If we start on '(', '{', ')', '}', etc., use the whole block inclusive.
1039      */
1040     if (!VIsual_active || EQUAL_POS(VIsual, curwin->w_cursor))
1041     {
1042 	setpcmark();
1043 	if (what == '{')		// ignore indent
1044 	    while (inindent(1))
1045 		if (inc_cursor() != 0)
1046 		    break;
1047 	if (gchar_cursor() == what)
1048 	    // cursor on '(' or '{', move cursor just after it
1049 	    ++curwin->w_cursor.col;
1050     }
1051     else if (LT_POS(VIsual, curwin->w_cursor))
1052     {
1053 	old_start = VIsual;
1054 	curwin->w_cursor = VIsual;	    // cursor at low end of Visual
1055     }
1056     else
1057 	old_end = VIsual;
1058 
1059     /*
1060      * Search backwards for unclosed '(', '{', etc..
1061      * Put this position in start_pos.
1062      * Ignore quotes here.  Keep the "M" flag in 'cpo', as that is what the
1063      * user wants.
1064      */
1065     save_cpo = p_cpo;
1066     p_cpo = (char_u *)(vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%");
1067     while (count-- > 0)
1068     {
1069 	if ((pos = findmatch(NULL, what)) == NULL)
1070 	    break;
1071 	curwin->w_cursor = *pos;
1072 	start_pos = *pos;   // the findmatch for end_pos will overwrite *pos
1073     }
1074     p_cpo = save_cpo;
1075 
1076     /*
1077      * Search for matching ')', '}', etc.
1078      * Put this position in curwin->w_cursor.
1079      */
1080     if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL)
1081     {
1082 	curwin->w_cursor = old_pos;
1083 	return FAIL;
1084     }
1085     curwin->w_cursor = *end_pos;
1086 
1087     /*
1088      * Try to exclude the '(', '{', ')', '}', etc. when "include" is FALSE.
1089      * If the ending '}', ')' or ']' is only preceded by indent, skip that
1090      * indent.  But only if the resulting area is not smaller than what we
1091      * started with.
1092      */
1093     while (!include)
1094     {
1095 	incl(&start_pos);
1096 	sol = (curwin->w_cursor.col == 0);
1097 	decl(&curwin->w_cursor);
1098 	while (inindent(1))
1099 	{
1100 	    sol = TRUE;
1101 	    if (decl(&curwin->w_cursor) != 0)
1102 		break;
1103 	}
1104 
1105 	/*
1106 	 * In Visual mode, when the resulting area is not bigger than what we
1107 	 * started with, extend it to the next block, and then exclude again.
1108 	 */
1109 	if (!LT_POS(start_pos, old_start) && !LT_POS(old_end, curwin->w_cursor)
1110 		&& VIsual_active)
1111 	{
1112 	    curwin->w_cursor = old_start;
1113 	    decl(&curwin->w_cursor);
1114 	    if ((pos = findmatch(NULL, what)) == NULL)
1115 	    {
1116 		curwin->w_cursor = old_pos;
1117 		return FAIL;
1118 	    }
1119 	    start_pos = *pos;
1120 	    curwin->w_cursor = *pos;
1121 	    if ((end_pos = findmatch(NULL, other)) == NULL)
1122 	    {
1123 		curwin->w_cursor = old_pos;
1124 		return FAIL;
1125 	    }
1126 	    curwin->w_cursor = *end_pos;
1127 	}
1128 	else
1129 	    break;
1130     }
1131 
1132     if (VIsual_active)
1133     {
1134 	if (*p_sel == 'e')
1135 	    inc(&curwin->w_cursor);
1136 	if (sol && gchar_cursor() != NUL)
1137 	    inc(&curwin->w_cursor);	// include the line break
1138 	VIsual = start_pos;
1139 	VIsual_mode = 'v';
1140 	redraw_curbuf_later(INVERTED);	// update the inversion
1141 	showmode();
1142     }
1143     else
1144     {
1145 	oap->start = start_pos;
1146 	oap->motion_type = MCHAR;
1147 	oap->inclusive = FALSE;
1148 	if (sol)
1149 	    incl(&curwin->w_cursor);
1150 	else if (LTOREQ_POS(start_pos, curwin->w_cursor))
1151 	    // Include the character under the cursor.
1152 	    oap->inclusive = TRUE;
1153 	else
1154 	    // End is before the start (no text in between <>, [], etc.): don't
1155 	    // operate on any text.
1156 	    curwin->w_cursor = start_pos;
1157     }
1158 
1159     return OK;
1160 }
1161 
1162 /*
1163  * Return TRUE if the cursor is on a "<aaa>" tag.  Ignore "<aaa/>".
1164  * When "end_tag" is TRUE return TRUE if the cursor is on "</aaa>".
1165  */
1166     static int
1167 in_html_tag(
1168     int		end_tag)
1169 {
1170     char_u	*line = ml_get_curline();
1171     char_u	*p;
1172     int		c;
1173     int		lc = NUL;
1174     pos_T	pos;
1175 
1176     if (enc_dbcs)
1177     {
1178 	char_u	*lp = NULL;
1179 
1180 	// We search forward until the cursor, because searching backwards is
1181 	// very slow for DBCS encodings.
1182 	for (p = line; p < line + curwin->w_cursor.col; MB_PTR_ADV(p))
1183 	    if (*p == '>' || *p == '<')
1184 	    {
1185 		lc = *p;
1186 		lp = p;
1187 	    }
1188 	if (*p != '<')	    // check for '<' under cursor
1189 	{
1190 	    if (lc != '<')
1191 		return FALSE;
1192 	    p = lp;
1193 	}
1194     }
1195     else
1196     {
1197 	for (p = line + curwin->w_cursor.col; p > line; )
1198 	{
1199 	    if (*p == '<')	// find '<' under/before cursor
1200 		break;
1201 	    MB_PTR_BACK(line, p);
1202 	    if (*p == '>')	// find '>' before cursor
1203 		break;
1204 	}
1205 	if (*p != '<')
1206 	    return FALSE;
1207     }
1208 
1209     pos.lnum = curwin->w_cursor.lnum;
1210     pos.col = (colnr_T)(p - line);
1211 
1212     MB_PTR_ADV(p);
1213     if (end_tag)
1214 	// check that there is a '/' after the '<'
1215 	return *p == '/';
1216 
1217     // check that there is no '/' after the '<'
1218     if (*p == '/')
1219 	return FALSE;
1220 
1221     // check that the matching '>' is not preceded by '/'
1222     for (;;)
1223     {
1224 	if (inc(&pos) < 0)
1225 	    return FALSE;
1226 	c = *ml_get_pos(&pos);
1227 	if (c == '>')
1228 	    break;
1229 	lc = c;
1230     }
1231     return lc != '/';
1232 }
1233 
1234 /*
1235  * Find tag block under the cursor, cursor at end.
1236  */
1237     int
1238 current_tagblock(
1239     oparg_T	*oap,
1240     long	count_arg,
1241     int		include)	// TRUE == include white space
1242 {
1243     long	count = count_arg;
1244     long	n;
1245     pos_T	old_pos;
1246     pos_T	start_pos;
1247     pos_T	end_pos;
1248     pos_T	old_start, old_end;
1249     char_u	*spat, *epat;
1250     char_u	*p;
1251     char_u	*cp;
1252     int		len;
1253     int		r;
1254     int		do_include = include;
1255     int		save_p_ws = p_ws;
1256     int		retval = FAIL;
1257     int		is_inclusive = TRUE;
1258 
1259     p_ws = FALSE;
1260 
1261     old_pos = curwin->w_cursor;
1262     old_end = curwin->w_cursor;		    // remember where we started
1263     old_start = old_end;
1264     if (!VIsual_active || *p_sel == 'e')
1265 	decl(&old_end);			    // old_end is inclusive
1266 
1267     /*
1268      * If we start on "<aaa>" select that block.
1269      */
1270     if (!VIsual_active || EQUAL_POS(VIsual, curwin->w_cursor))
1271     {
1272 	setpcmark();
1273 
1274 	// ignore indent
1275 	while (inindent(1))
1276 	    if (inc_cursor() != 0)
1277 		break;
1278 
1279 	if (in_html_tag(FALSE))
1280 	{
1281 	    // cursor on start tag, move to its '>'
1282 	    while (*ml_get_cursor() != '>')
1283 		if (inc_cursor() < 0)
1284 		    break;
1285 	}
1286 	else if (in_html_tag(TRUE))
1287 	{
1288 	    // cursor on end tag, move to just before it
1289 	    while (*ml_get_cursor() != '<')
1290 		if (dec_cursor() < 0)
1291 		    break;
1292 	    dec_cursor();
1293 	    old_end = curwin->w_cursor;
1294 	}
1295     }
1296     else if (LT_POS(VIsual, curwin->w_cursor))
1297     {
1298 	old_start = VIsual;
1299 	curwin->w_cursor = VIsual;	    // cursor at low end of Visual
1300     }
1301     else
1302 	old_end = VIsual;
1303 
1304 again:
1305     /*
1306      * Search backwards for unclosed "<aaa>".
1307      * Put this position in start_pos.
1308      */
1309     for (n = 0; n < count; ++n)
1310     {
1311 	if (do_searchpair((char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
1312 		    (char_u *)"",
1313 		    (char_u *)"</[^>]*>", BACKWARD, NULL, 0,
1314 						  NULL, (linenr_T)0, 0L) <= 0)
1315 	{
1316 	    curwin->w_cursor = old_pos;
1317 	    goto theend;
1318 	}
1319     }
1320     start_pos = curwin->w_cursor;
1321 
1322     /*
1323      * Search for matching "</aaa>".  First isolate the "aaa".
1324      */
1325     inc_cursor();
1326     p = ml_get_cursor();
1327     for (cp = p; *cp != NUL && *cp != '>' && !VIM_ISWHITE(*cp); MB_PTR_ADV(cp))
1328 	;
1329     len = (int)(cp - p);
1330     if (len == 0)
1331     {
1332 	curwin->w_cursor = old_pos;
1333 	goto theend;
1334     }
1335     spat = alloc(len + 31);
1336     epat = alloc(len + 9);
1337     if (spat == NULL || epat == NULL)
1338     {
1339 	vim_free(spat);
1340 	vim_free(epat);
1341 	curwin->w_cursor = old_pos;
1342 	goto theend;
1343     }
1344     sprintf((char *)spat, "<%.*s\\>\\%%(\\s\\_[^>]\\{-}[^/]>\\|>\\)\\c", len, p);
1345     sprintf((char *)epat, "</%.*s>\\c", len, p);
1346 
1347     r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL,
1348 						    0, NULL, (linenr_T)0, 0L);
1349 
1350     vim_free(spat);
1351     vim_free(epat);
1352 
1353     if (r < 1 || LT_POS(curwin->w_cursor, old_end))
1354     {
1355 	// Can't find other end or it's before the previous end.  Could be a
1356 	// HTML tag that doesn't have a matching end.  Search backwards for
1357 	// another starting tag.
1358 	count = 1;
1359 	curwin->w_cursor = start_pos;
1360 	goto again;
1361     }
1362 
1363     if (do_include)
1364     {
1365 	// Include up to the '>'.
1366 	while (*ml_get_cursor() != '>')
1367 	    if (inc_cursor() < 0)
1368 		break;
1369     }
1370     else
1371     {
1372 	char_u *c = ml_get_cursor();
1373 
1374 	// Exclude the '<' of the end tag.
1375 	// If the closing tag is on new line, do not decrement cursor, but
1376 	// make operation exclusive, so that the linefeed will be selected
1377 	if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0)
1378 	    // do not decrement cursor
1379 	    is_inclusive = FALSE;
1380 	else if (*c == '<')
1381 	    dec_cursor();
1382     }
1383     end_pos = curwin->w_cursor;
1384 
1385     if (!do_include)
1386     {
1387 	// Exclude the start tag.
1388 	curwin->w_cursor = start_pos;
1389 	while (inc_cursor() >= 0)
1390 	    if (*ml_get_cursor() == '>')
1391 	    {
1392 		inc_cursor();
1393 		start_pos = curwin->w_cursor;
1394 		break;
1395 	    }
1396 	curwin->w_cursor = end_pos;
1397 
1398 	// If we are in Visual mode and now have the same text as before set
1399 	// "do_include" and try again.
1400 	if (VIsual_active && EQUAL_POS(start_pos, old_start)
1401 						&& EQUAL_POS(end_pos, old_end))
1402 	{
1403 	    do_include = TRUE;
1404 	    curwin->w_cursor = old_start;
1405 	    count = count_arg;
1406 	    goto again;
1407 	}
1408     }
1409 
1410     if (VIsual_active)
1411     {
1412 	// If the end is before the start there is no text between tags, select
1413 	// the char under the cursor.
1414 	if (LT_POS(end_pos, start_pos))
1415 	    curwin->w_cursor = start_pos;
1416 	else if (*p_sel == 'e')
1417 	    inc_cursor();
1418 	VIsual = start_pos;
1419 	VIsual_mode = 'v';
1420 	redraw_curbuf_later(INVERTED);	// update the inversion
1421 	showmode();
1422     }
1423     else
1424     {
1425 	oap->start = start_pos;
1426 	oap->motion_type = MCHAR;
1427 	if (LT_POS(end_pos, start_pos))
1428 	{
1429 	    // End is before the start: there is no text between tags; operate
1430 	    // on an empty area.
1431 	    curwin->w_cursor = start_pos;
1432 	    oap->inclusive = FALSE;
1433 	}
1434 	else
1435 	    oap->inclusive = is_inclusive;
1436     }
1437     retval = OK;
1438 
1439 theend:
1440     p_ws = save_p_ws;
1441     return retval;
1442 }
1443 
1444     int
1445 current_par(
1446     oparg_T	*oap,
1447     long	count,
1448     int		include,	// TRUE == include white space
1449     int		type)		// 'p' for paragraph, 'S' for section
1450 {
1451     linenr_T	start_lnum;
1452     linenr_T	end_lnum;
1453     int		white_in_front;
1454     int		dir;
1455     int		start_is_white;
1456     int		prev_start_is_white;
1457     int		retval = OK;
1458     int		do_white = FALSE;
1459     int		t;
1460     int		i;
1461 
1462     if (type == 'S')	    // not implemented yet
1463 	return FAIL;
1464 
1465     start_lnum = curwin->w_cursor.lnum;
1466 
1467     /*
1468      * When visual area is more than one line: extend it.
1469      */
1470     if (VIsual_active && start_lnum != VIsual.lnum)
1471     {
1472 extend:
1473 	if (start_lnum < VIsual.lnum)
1474 	    dir = BACKWARD;
1475 	else
1476 	    dir = FORWARD;
1477 	for (i = count; --i >= 0; )
1478 	{
1479 	    if (start_lnum ==
1480 			   (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count))
1481 	    {
1482 		retval = FAIL;
1483 		break;
1484 	    }
1485 
1486 	    prev_start_is_white = -1;
1487 	    for (t = 0; t < 2; ++t)
1488 	    {
1489 		start_lnum += dir;
1490 		start_is_white = linewhite(start_lnum);
1491 		if (prev_start_is_white == start_is_white)
1492 		{
1493 		    start_lnum -= dir;
1494 		    break;
1495 		}
1496 		for (;;)
1497 		{
1498 		    if (start_lnum == (dir == BACKWARD
1499 					    ? 1 : curbuf->b_ml.ml_line_count))
1500 			break;
1501 		    if (start_is_white != linewhite(start_lnum + dir)
1502 			    || (!start_is_white
1503 				    && startPS(start_lnum + (dir > 0
1504 							     ? 1 : 0), 0, 0)))
1505 			break;
1506 		    start_lnum += dir;
1507 		}
1508 		if (!include)
1509 		    break;
1510 		if (start_lnum == (dir == BACKWARD
1511 					    ? 1 : curbuf->b_ml.ml_line_count))
1512 		    break;
1513 		prev_start_is_white = start_is_white;
1514 	    }
1515 	}
1516 	curwin->w_cursor.lnum = start_lnum;
1517 	curwin->w_cursor.col = 0;
1518 	return retval;
1519     }
1520 
1521     /*
1522      * First move back to the start_lnum of the paragraph or white lines
1523      */
1524     white_in_front = linewhite(start_lnum);
1525     while (start_lnum > 1)
1526     {
1527 	if (white_in_front)	    // stop at first white line
1528 	{
1529 	    if (!linewhite(start_lnum - 1))
1530 		break;
1531 	}
1532 	else		// stop at first non-white line of start of paragraph
1533 	{
1534 	    if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0))
1535 		break;
1536 	}
1537 	--start_lnum;
1538     }
1539 
1540     /*
1541      * Move past the end of any white lines.
1542      */
1543     end_lnum = start_lnum;
1544     while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum))
1545 	++end_lnum;
1546 
1547     --end_lnum;
1548     i = count;
1549     if (!include && white_in_front)
1550 	--i;
1551     while (i--)
1552     {
1553 	if (end_lnum == curbuf->b_ml.ml_line_count)
1554 	    return FAIL;
1555 
1556 	if (!include)
1557 	    do_white = linewhite(end_lnum + 1);
1558 
1559 	if (include || !do_white)
1560 	{
1561 	    ++end_lnum;
1562 	    /*
1563 	     * skip to end of paragraph
1564 	     */
1565 	    while (end_lnum < curbuf->b_ml.ml_line_count
1566 		    && !linewhite(end_lnum + 1)
1567 		    && !startPS(end_lnum + 1, 0, 0))
1568 		++end_lnum;
1569 	}
1570 
1571 	if (i == 0 && white_in_front && include)
1572 	    break;
1573 
1574 	/*
1575 	 * skip to end of white lines after paragraph
1576 	 */
1577 	if (include || do_white)
1578 	    while (end_lnum < curbuf->b_ml.ml_line_count
1579 						   && linewhite(end_lnum + 1))
1580 		++end_lnum;
1581     }
1582 
1583     /*
1584      * If there are no empty lines at the end, try to find some empty lines at
1585      * the start (unless that has been done already).
1586      */
1587     if (!white_in_front && !linewhite(end_lnum) && include)
1588 	while (start_lnum > 1 && linewhite(start_lnum - 1))
1589 	    --start_lnum;
1590 
1591     if (VIsual_active)
1592     {
1593 	// Problem: when doing "Vipipip" nothing happens in a single white
1594 	// line, we get stuck there.  Trap this here.
1595 	if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum)
1596 	    goto extend;
1597 	if (VIsual.lnum != start_lnum)
1598 	{
1599 	    VIsual.lnum = start_lnum;
1600 	    VIsual.col = 0;
1601 	}
1602 	VIsual_mode = 'V';
1603 	redraw_curbuf_later(INVERTED);	// update the inversion
1604 	showmode();
1605     }
1606     else
1607     {
1608 	oap->start.lnum = start_lnum;
1609 	oap->start.col = 0;
1610 	oap->motion_type = MLINE;
1611     }
1612     curwin->w_cursor.lnum = end_lnum;
1613     curwin->w_cursor.col = 0;
1614 
1615     return OK;
1616 }
1617 
1618 /*
1619  * Search quote char from string line[col].
1620  * Quote character escaped by one of the characters in "escape" is not counted
1621  * as a quote.
1622  * Returns column number of "quotechar" or -1 when not found.
1623  */
1624     static int
1625 find_next_quote(
1626     char_u	*line,
1627     int		col,
1628     int		quotechar,
1629     char_u	*escape)	// escape characters, can be NULL
1630 {
1631     int		c;
1632 
1633     for (;;)
1634     {
1635 	c = line[col];
1636 	if (c == NUL)
1637 	    return -1;
1638 	else if (escape != NULL && vim_strchr(escape, c))
1639 	    ++col;
1640 	else if (c == quotechar)
1641 	    break;
1642 	if (has_mbyte)
1643 	    col += (*mb_ptr2len)(line + col);
1644 	else
1645 	    ++col;
1646     }
1647     return col;
1648 }
1649 
1650 /*
1651  * Search backwards in "line" from column "col_start" to find "quotechar".
1652  * Quote character escaped by one of the characters in "escape" is not counted
1653  * as a quote.
1654  * Return the found column or zero.
1655  */
1656     static int
1657 find_prev_quote(
1658     char_u	*line,
1659     int		col_start,
1660     int		quotechar,
1661     char_u	*escape)	// escape characters, can be NULL
1662 {
1663     int		n;
1664 
1665     while (col_start > 0)
1666     {
1667 	--col_start;
1668 	col_start -= (*mb_head_off)(line, line + col_start);
1669 	n = 0;
1670 	if (escape != NULL)
1671 	    while (col_start - n > 0 && vim_strchr(escape,
1672 					     line[col_start - n - 1]) != NULL)
1673 	    ++n;
1674 	if (n & 1)
1675 	    col_start -= n;	// uneven number of escape chars, skip it
1676 	else if (line[col_start] == quotechar)
1677 	    break;
1678     }
1679     return col_start;
1680 }
1681 
1682 /*
1683  * Find quote under the cursor, cursor at end.
1684  * Returns TRUE if found, else FALSE.
1685  */
1686     int
1687 current_quote(
1688     oparg_T	*oap,
1689     long	count,
1690     int		include,	// TRUE == include quote char
1691     int		quotechar)	// Quote character
1692 {
1693     char_u	*line = ml_get_curline();
1694     int		col_end;
1695     int		col_start = curwin->w_cursor.col;
1696     int		inclusive = FALSE;
1697     int		vis_empty = TRUE;	// Visual selection <= 1 char
1698     int		vis_bef_curs = FALSE;	// Visual starts before cursor
1699     int		did_exclusive_adj = FALSE;  // adjusted pos for 'selection'
1700     int		inside_quotes = FALSE;	// Looks like "i'" done before
1701     int		selected_quote = FALSE;	// Has quote inside selection
1702     int		i;
1703     int		restore_vis_bef = FALSE; // restore VIsual on abort
1704 
1705     // When 'selection' is "exclusive" move the cursor to where it would be
1706     // with 'selection' "inclusive", so that the logic is the same for both.
1707     // The cursor then is moved forward after adjusting the area.
1708     if (VIsual_active)
1709     {
1710 	// this only works within one line
1711 	if (VIsual.lnum != curwin->w_cursor.lnum)
1712 	    return FALSE;
1713 
1714 	vis_bef_curs = LT_POS(VIsual, curwin->w_cursor);
1715 	vis_empty = EQUAL_POS(VIsual, curwin->w_cursor);
1716 	if (*p_sel == 'e')
1717 	{
1718 	    if (vis_bef_curs)
1719 	    {
1720 		dec_cursor();
1721 		did_exclusive_adj = TRUE;
1722 	    }
1723 	    else if (!vis_empty)
1724 	    {
1725 		dec(&VIsual);
1726 		did_exclusive_adj = TRUE;
1727 	    }
1728 	    vis_empty = EQUAL_POS(VIsual, curwin->w_cursor);
1729 	    if (!vis_bef_curs && !vis_empty)
1730 	    {
1731 		// VIsual needs to be the start of Visual selection.
1732 		pos_T t = curwin->w_cursor;
1733 
1734 		curwin->w_cursor = VIsual;
1735 		VIsual = t;
1736 		vis_bef_curs = TRUE;
1737 		restore_vis_bef = TRUE;
1738 	    }
1739 	}
1740     }
1741 
1742     if (!vis_empty)
1743     {
1744 	// Check if the existing selection exactly spans the text inside
1745 	// quotes.
1746 	if (vis_bef_curs)
1747 	{
1748 	    inside_quotes = VIsual.col > 0
1749 			&& line[VIsual.col - 1] == quotechar
1750 			&& line[curwin->w_cursor.col] != NUL
1751 			&& line[curwin->w_cursor.col + 1] == quotechar;
1752 	    i = VIsual.col;
1753 	    col_end = curwin->w_cursor.col;
1754 	}
1755 	else
1756 	{
1757 	    inside_quotes = curwin->w_cursor.col > 0
1758 			&& line[curwin->w_cursor.col - 1] == quotechar
1759 			&& line[VIsual.col] != NUL
1760 			&& line[VIsual.col + 1] == quotechar;
1761 	    i = curwin->w_cursor.col;
1762 	    col_end = VIsual.col;
1763 	}
1764 
1765 	// Find out if we have a quote in the selection.
1766 	while (i <= col_end)
1767 	    if (line[i++] == quotechar)
1768 	    {
1769 		selected_quote = TRUE;
1770 		break;
1771 	    }
1772     }
1773 
1774     if (!vis_empty && line[col_start] == quotechar)
1775     {
1776 	// Already selecting something and on a quote character.  Find the
1777 	// next quoted string.
1778 	if (vis_bef_curs)
1779 	{
1780 	    // Assume we are on a closing quote: move to after the next
1781 	    // opening quote.
1782 	    col_start = find_next_quote(line, col_start + 1, quotechar, NULL);
1783 	    if (col_start < 0)
1784 		goto abort_search;
1785 	    col_end = find_next_quote(line, col_start + 1, quotechar,
1786 							      curbuf->b_p_qe);
1787 	    if (col_end < 0)
1788 	    {
1789 		// We were on a starting quote perhaps?
1790 		col_end = col_start;
1791 		col_start = curwin->w_cursor.col;
1792 	    }
1793 	}
1794 	else
1795 	{
1796 	    col_end = find_prev_quote(line, col_start, quotechar, NULL);
1797 	    if (line[col_end] != quotechar)
1798 		goto abort_search;
1799 	    col_start = find_prev_quote(line, col_end, quotechar,
1800 							      curbuf->b_p_qe);
1801 	    if (line[col_start] != quotechar)
1802 	    {
1803 		// We were on an ending quote perhaps?
1804 		col_start = col_end;
1805 		col_end = curwin->w_cursor.col;
1806 	    }
1807 	}
1808     }
1809     else
1810 
1811     if (line[col_start] == quotechar || !vis_empty)
1812     {
1813 	int	first_col = col_start;
1814 
1815 	if (!vis_empty)
1816 	{
1817 	    if (vis_bef_curs)
1818 		first_col = find_next_quote(line, col_start, quotechar, NULL);
1819 	    else
1820 		first_col = find_prev_quote(line, col_start, quotechar, NULL);
1821 	}
1822 
1823 	// The cursor is on a quote, we don't know if it's the opening or
1824 	// closing quote.  Search from the start of the line to find out.
1825 	// Also do this when there is a Visual area, a' may leave the cursor
1826 	// in between two strings.
1827 	col_start = 0;
1828 	for (;;)
1829 	{
1830 	    // Find open quote character.
1831 	    col_start = find_next_quote(line, col_start, quotechar, NULL);
1832 	    if (col_start < 0 || col_start > first_col)
1833 		goto abort_search;
1834 	    // Find close quote character.
1835 	    col_end = find_next_quote(line, col_start + 1, quotechar,
1836 							      curbuf->b_p_qe);
1837 	    if (col_end < 0)
1838 		goto abort_search;
1839 	    // If is cursor between start and end quote character, it is
1840 	    // target text object.
1841 	    if (col_start <= first_col && first_col <= col_end)
1842 		break;
1843 	    col_start = col_end + 1;
1844 	}
1845     }
1846     else
1847     {
1848 	// Search backward for a starting quote.
1849 	col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe);
1850 	if (line[col_start] != quotechar)
1851 	{
1852 	    // No quote before the cursor, look after the cursor.
1853 	    col_start = find_next_quote(line, col_start, quotechar, NULL);
1854 	    if (col_start < 0)
1855 		goto abort_search;
1856 	}
1857 
1858 	// Find close quote character.
1859 	col_end = find_next_quote(line, col_start + 1, quotechar,
1860 							      curbuf->b_p_qe);
1861 	if (col_end < 0)
1862 	    goto abort_search;
1863     }
1864 
1865     // When "include" is TRUE, include spaces after closing quote or before
1866     // the starting quote.
1867     if (include)
1868     {
1869 	if (VIM_ISWHITE(line[col_end + 1]))
1870 	    while (VIM_ISWHITE(line[col_end + 1]))
1871 		++col_end;
1872 	else
1873 	    while (col_start > 0 && VIM_ISWHITE(line[col_start - 1]))
1874 		--col_start;
1875     }
1876 
1877     // Set start position.  After vi" another i" must include the ".
1878     // For v2i" include the quotes.
1879     if (!include && count < 2 && (vis_empty || !inside_quotes))
1880 	++col_start;
1881     curwin->w_cursor.col = col_start;
1882     if (VIsual_active)
1883     {
1884 	// Set the start of the Visual area when the Visual area was empty, we
1885 	// were just inside quotes or the Visual area didn't start at a quote
1886 	// and didn't include a quote.
1887 	if (vis_empty
1888 		|| (vis_bef_curs
1889 		    && !selected_quote
1890 		    && (inside_quotes
1891 			|| (line[VIsual.col] != quotechar
1892 			    && (VIsual.col == 0
1893 				|| line[VIsual.col - 1] != quotechar)))))
1894 	{
1895 	    VIsual = curwin->w_cursor;
1896 	    redraw_curbuf_later(INVERTED);
1897 	}
1898     }
1899     else
1900     {
1901 	oap->start = curwin->w_cursor;
1902 	oap->motion_type = MCHAR;
1903     }
1904 
1905     // Set end position.
1906     curwin->w_cursor.col = col_end;
1907     if ((include || count > 1 // After vi" another i" must include the ".
1908 		|| (!vis_empty && inside_quotes)
1909 	) && inc_cursor() == 2)
1910 	inclusive = TRUE;
1911     if (VIsual_active)
1912     {
1913 	if (vis_empty || vis_bef_curs)
1914 	{
1915 	    // decrement cursor when 'selection' is not exclusive
1916 	    if (*p_sel != 'e')
1917 		dec_cursor();
1918 	}
1919 	else
1920 	{
1921 	    // Cursor is at start of Visual area.  Set the end of the Visual
1922 	    // area when it was just inside quotes or it didn't end at a
1923 	    // quote.
1924 	    if (inside_quotes
1925 		    || (!selected_quote
1926 			&& line[VIsual.col] != quotechar
1927 			&& (line[VIsual.col] == NUL
1928 			    || line[VIsual.col + 1] != quotechar)))
1929 	    {
1930 		dec_cursor();
1931 		VIsual = curwin->w_cursor;
1932 	    }
1933 	    curwin->w_cursor.col = col_start;
1934 	}
1935 	if (VIsual_mode == 'V')
1936 	{
1937 	    VIsual_mode = 'v';
1938 	    redraw_cmdline = TRUE;		// show mode later
1939 	}
1940     }
1941     else
1942     {
1943 	// Set inclusive and other oap's flags.
1944 	oap->inclusive = inclusive;
1945     }
1946 
1947     return OK;
1948 
1949 abort_search:
1950     if (VIsual_active && *p_sel == 'e')
1951     {
1952 	if (did_exclusive_adj)
1953 	    inc_cursor();
1954 	if (restore_vis_bef)
1955 	{
1956 	    pos_T t = curwin->w_cursor;
1957 
1958 	    curwin->w_cursor = VIsual;
1959 	    VIsual = t;
1960 	}
1961     }
1962     return FALSE;
1963 }
1964 
1965 #endif // FEAT_TEXTOBJ
1966