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