xref: /vim-8.2.3635/src/textobject.c (revision e30d1025)
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     if ((pos = findmatch(NULL, what)) != NULL)
1083     {
1084 	while (count-- > 0)
1085 	{
1086 	    if ((pos = findmatch(NULL, what)) == NULL)
1087 		break;
1088 	    curwin->w_cursor = *pos;
1089 	    start_pos = *pos;   // the findmatch for end_pos will overwrite *pos
1090 	}
1091     }
1092     else
1093     {
1094 	while (count-- > 0)
1095 	{
1096 	    if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL)
1097 		break;
1098 	    curwin->w_cursor = *pos;
1099 	    start_pos = *pos;   // the findmatch for end_pos will overwrite *pos
1100 	}
1101     }
1102     p_cpo = save_cpo;
1103 
1104     /*
1105      * Search for matching ')', '}', etc.
1106      * Put this position in curwin->w_cursor.
1107      */
1108     if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL)
1109     {
1110 	curwin->w_cursor = old_pos;
1111 	return FAIL;
1112     }
1113     curwin->w_cursor = *end_pos;
1114 
1115     /*
1116      * Try to exclude the '(', '{', ')', '}', etc. when "include" is FALSE.
1117      * If the ending '}', ')' or ']' is only preceded by indent, skip that
1118      * indent.  But only if the resulting area is not smaller than what we
1119      * started with.
1120      */
1121     while (!include)
1122     {
1123 	incl(&start_pos);
1124 	sol = (curwin->w_cursor.col == 0);
1125 	decl(&curwin->w_cursor);
1126 	while (inindent(1))
1127 	{
1128 	    sol = TRUE;
1129 	    if (decl(&curwin->w_cursor) != 0)
1130 		break;
1131 	}
1132 
1133 	/*
1134 	 * In Visual mode, when the resulting area is not bigger than what we
1135 	 * started with, extend it to the next block, and then exclude again.
1136 	 */
1137 	if (!LT_POS(start_pos, old_start) && !LT_POS(old_end, curwin->w_cursor)
1138 		&& VIsual_active)
1139 	{
1140 	    curwin->w_cursor = old_start;
1141 	    decl(&curwin->w_cursor);
1142 	    if ((pos = findmatch(NULL, what)) == NULL)
1143 	    {
1144 		curwin->w_cursor = old_pos;
1145 		return FAIL;
1146 	    }
1147 	    start_pos = *pos;
1148 	    curwin->w_cursor = *pos;
1149 	    if ((end_pos = findmatch(NULL, other)) == NULL)
1150 	    {
1151 		curwin->w_cursor = old_pos;
1152 		return FAIL;
1153 	    }
1154 	    curwin->w_cursor = *end_pos;
1155 	}
1156 	else
1157 	    break;
1158     }
1159 
1160     if (VIsual_active)
1161     {
1162 	if (*p_sel == 'e')
1163 	    inc(&curwin->w_cursor);
1164 	if (sol && gchar_cursor() != NUL)
1165 	    inc(&curwin->w_cursor);	// include the line break
1166 	VIsual = start_pos;
1167 	VIsual_mode = 'v';
1168 	redraw_curbuf_later(INVERTED);	// update the inversion
1169 	showmode();
1170     }
1171     else
1172     {
1173 	oap->start = start_pos;
1174 	oap->motion_type = MCHAR;
1175 	oap->inclusive = FALSE;
1176 	if (sol)
1177 	    incl(&curwin->w_cursor);
1178 	else if (LTOREQ_POS(start_pos, curwin->w_cursor))
1179 	    // Include the character under the cursor.
1180 	    oap->inclusive = TRUE;
1181 	else
1182 	    // End is before the start (no text in between <>, [], etc.): don't
1183 	    // operate on any text.
1184 	    curwin->w_cursor = start_pos;
1185     }
1186 
1187     return OK;
1188 }
1189 
1190 /*
1191  * Return TRUE if the cursor is on a "<aaa>" tag.  Ignore "<aaa/>".
1192  * When "end_tag" is TRUE return TRUE if the cursor is on "</aaa>".
1193  */
1194     static int
1195 in_html_tag(
1196     int		end_tag)
1197 {
1198     char_u	*line = ml_get_curline();
1199     char_u	*p;
1200     int		c;
1201     int		lc = NUL;
1202     pos_T	pos;
1203 
1204     if (enc_dbcs)
1205     {
1206 	char_u	*lp = NULL;
1207 
1208 	// We search forward until the cursor, because searching backwards is
1209 	// very slow for DBCS encodings.
1210 	for (p = line; p < line + curwin->w_cursor.col; MB_PTR_ADV(p))
1211 	    if (*p == '>' || *p == '<')
1212 	    {
1213 		lc = *p;
1214 		lp = p;
1215 	    }
1216 	if (*p != '<')	    // check for '<' under cursor
1217 	{
1218 	    if (lc != '<')
1219 		return FALSE;
1220 	    p = lp;
1221 	}
1222     }
1223     else
1224     {
1225 	for (p = line + curwin->w_cursor.col; p > line; )
1226 	{
1227 	    if (*p == '<')	// find '<' under/before cursor
1228 		break;
1229 	    MB_PTR_BACK(line, p);
1230 	    if (*p == '>')	// find '>' before cursor
1231 		break;
1232 	}
1233 	if (*p != '<')
1234 	    return FALSE;
1235     }
1236 
1237     pos.lnum = curwin->w_cursor.lnum;
1238     pos.col = (colnr_T)(p - line);
1239 
1240     MB_PTR_ADV(p);
1241     if (end_tag)
1242 	// check that there is a '/' after the '<'
1243 	return *p == '/';
1244 
1245     // check that there is no '/' after the '<'
1246     if (*p == '/')
1247 	return FALSE;
1248 
1249     // check that the matching '>' is not preceded by '/'
1250     for (;;)
1251     {
1252 	if (inc(&pos) < 0)
1253 	    return FALSE;
1254 	c = *ml_get_pos(&pos);
1255 	if (c == '>')
1256 	    break;
1257 	lc = c;
1258     }
1259     return lc != '/';
1260 }
1261 
1262 /*
1263  * Find tag block under the cursor, cursor at end.
1264  */
1265     int
1266 current_tagblock(
1267     oparg_T	*oap,
1268     long	count_arg,
1269     int		include)	// TRUE == include white space
1270 {
1271     long	count = count_arg;
1272     long	n;
1273     pos_T	old_pos;
1274     pos_T	start_pos;
1275     pos_T	end_pos;
1276     pos_T	old_start, old_end;
1277     char_u	*spat, *epat;
1278     char_u	*p;
1279     char_u	*cp;
1280     int		len;
1281     int		r;
1282     int		do_include = include;
1283     int		save_p_ws = p_ws;
1284     int		retval = FAIL;
1285     int		is_inclusive = TRUE;
1286 
1287     p_ws = FALSE;
1288 
1289     old_pos = curwin->w_cursor;
1290     old_end = curwin->w_cursor;		    // remember where we started
1291     old_start = old_end;
1292     if (!VIsual_active || *p_sel == 'e')
1293 	decl(&old_end);			    // old_end is inclusive
1294 
1295     /*
1296      * If we start on "<aaa>" select that block.
1297      */
1298     if (!VIsual_active || EQUAL_POS(VIsual, curwin->w_cursor))
1299     {
1300 	setpcmark();
1301 
1302 	// ignore indent
1303 	while (inindent(1))
1304 	    if (inc_cursor() != 0)
1305 		break;
1306 
1307 	if (in_html_tag(FALSE))
1308 	{
1309 	    // cursor on start tag, move to its '>'
1310 	    while (*ml_get_cursor() != '>')
1311 		if (inc_cursor() < 0)
1312 		    break;
1313 	}
1314 	else if (in_html_tag(TRUE))
1315 	{
1316 	    // cursor on end tag, move to just before it
1317 	    while (*ml_get_cursor() != '<')
1318 		if (dec_cursor() < 0)
1319 		    break;
1320 	    dec_cursor();
1321 	    old_end = curwin->w_cursor;
1322 	}
1323     }
1324     else if (LT_POS(VIsual, curwin->w_cursor))
1325     {
1326 	old_start = VIsual;
1327 	curwin->w_cursor = VIsual;	    // cursor at low end of Visual
1328     }
1329     else
1330 	old_end = VIsual;
1331 
1332 again:
1333     /*
1334      * Search backwards for unclosed "<aaa>".
1335      * Put this position in start_pos.
1336      */
1337     for (n = 0; n < count; ++n)
1338     {
1339 	if (do_searchpair((char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
1340 		    (char_u *)"",
1341 		    (char_u *)"</[^>]*>", BACKWARD, NULL, 0,
1342 						  NULL, (linenr_T)0, 0L) <= 0)
1343 	{
1344 	    curwin->w_cursor = old_pos;
1345 	    goto theend;
1346 	}
1347     }
1348     start_pos = curwin->w_cursor;
1349 
1350     /*
1351      * Search for matching "</aaa>".  First isolate the "aaa".
1352      */
1353     inc_cursor();
1354     p = ml_get_cursor();
1355     for (cp = p; *cp != NUL && *cp != '>' && !VIM_ISWHITE(*cp); MB_PTR_ADV(cp))
1356 	;
1357     len = (int)(cp - p);
1358     if (len == 0)
1359     {
1360 	curwin->w_cursor = old_pos;
1361 	goto theend;
1362     }
1363     spat = alloc(len + 39);
1364     epat = alloc(len + 9);
1365     if (spat == NULL || epat == NULL)
1366     {
1367 	vim_free(spat);
1368 	vim_free(epat);
1369 	curwin->w_cursor = old_pos;
1370 	goto theend;
1371     }
1372     sprintf((char *)spat, "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p);
1373     sprintf((char *)epat, "</%.*s>\\c", len, p);
1374 
1375     r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL,
1376 						    0, NULL, (linenr_T)0, 0L);
1377 
1378     vim_free(spat);
1379     vim_free(epat);
1380 
1381     if (r < 1 || LT_POS(curwin->w_cursor, old_end))
1382     {
1383 	// Can't find other end or it's before the previous end.  Could be a
1384 	// HTML tag that doesn't have a matching end.  Search backwards for
1385 	// another starting tag.
1386 	count = 1;
1387 	curwin->w_cursor = start_pos;
1388 	goto again;
1389     }
1390 
1391     if (do_include)
1392     {
1393 	// Include up to the '>'.
1394 	while (*ml_get_cursor() != '>')
1395 	    if (inc_cursor() < 0)
1396 		break;
1397     }
1398     else
1399     {
1400 	char_u *c = ml_get_cursor();
1401 
1402 	// Exclude the '<' of the end tag.
1403 	// If the closing tag is on new line, do not decrement cursor, but
1404 	// make operation exclusive, so that the linefeed will be selected
1405 	if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0)
1406 	    // do not decrement cursor
1407 	    is_inclusive = FALSE;
1408 	else if (*c == '<')
1409 	    dec_cursor();
1410     }
1411     end_pos = curwin->w_cursor;
1412 
1413     if (!do_include)
1414     {
1415 	// Exclude the start tag.
1416 	curwin->w_cursor = start_pos;
1417 	while (inc_cursor() >= 0)
1418 	    if (*ml_get_cursor() == '>')
1419 	    {
1420 		inc_cursor();
1421 		start_pos = curwin->w_cursor;
1422 		break;
1423 	    }
1424 	curwin->w_cursor = end_pos;
1425 
1426 	// If we are in Visual mode and now have the same text as before set
1427 	// "do_include" and try again.
1428 	if (VIsual_active && EQUAL_POS(start_pos, old_start)
1429 						&& EQUAL_POS(end_pos, old_end))
1430 	{
1431 	    do_include = TRUE;
1432 	    curwin->w_cursor = old_start;
1433 	    count = count_arg;
1434 	    goto again;
1435 	}
1436     }
1437 
1438     if (VIsual_active)
1439     {
1440 	// If the end is before the start there is no text between tags, select
1441 	// the char under the cursor.
1442 	if (LT_POS(end_pos, start_pos))
1443 	    curwin->w_cursor = start_pos;
1444 	else if (*p_sel == 'e')
1445 	    inc_cursor();
1446 	VIsual = start_pos;
1447 	VIsual_mode = 'v';
1448 	redraw_curbuf_later(INVERTED);	// update the inversion
1449 	showmode();
1450     }
1451     else
1452     {
1453 	oap->start = start_pos;
1454 	oap->motion_type = MCHAR;
1455 	if (LT_POS(end_pos, start_pos))
1456 	{
1457 	    // End is before the start: there is no text between tags; operate
1458 	    // on an empty area.
1459 	    curwin->w_cursor = start_pos;
1460 	    oap->inclusive = FALSE;
1461 	}
1462 	else
1463 	    oap->inclusive = is_inclusive;
1464     }
1465     retval = OK;
1466 
1467 theend:
1468     p_ws = save_p_ws;
1469     return retval;
1470 }
1471 
1472     int
1473 current_par(
1474     oparg_T	*oap,
1475     long	count,
1476     int		include,	// TRUE == include white space
1477     int		type)		// 'p' for paragraph, 'S' for section
1478 {
1479     linenr_T	start_lnum;
1480     linenr_T	end_lnum;
1481     int		white_in_front;
1482     int		dir;
1483     int		start_is_white;
1484     int		prev_start_is_white;
1485     int		retval = OK;
1486     int		do_white = FALSE;
1487     int		t;
1488     int		i;
1489 
1490     if (type == 'S')	    // not implemented yet
1491 	return FAIL;
1492 
1493     start_lnum = curwin->w_cursor.lnum;
1494 
1495     /*
1496      * When visual area is more than one line: extend it.
1497      */
1498     if (VIsual_active && start_lnum != VIsual.lnum)
1499     {
1500 extend:
1501 	if (start_lnum < VIsual.lnum)
1502 	    dir = BACKWARD;
1503 	else
1504 	    dir = FORWARD;
1505 	for (i = count; --i >= 0; )
1506 	{
1507 	    if (start_lnum ==
1508 			   (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count))
1509 	    {
1510 		retval = FAIL;
1511 		break;
1512 	    }
1513 
1514 	    prev_start_is_white = -1;
1515 	    for (t = 0; t < 2; ++t)
1516 	    {
1517 		start_lnum += dir;
1518 		start_is_white = linewhite(start_lnum);
1519 		if (prev_start_is_white == start_is_white)
1520 		{
1521 		    start_lnum -= dir;
1522 		    break;
1523 		}
1524 		for (;;)
1525 		{
1526 		    if (start_lnum == (dir == BACKWARD
1527 					    ? 1 : curbuf->b_ml.ml_line_count))
1528 			break;
1529 		    if (start_is_white != linewhite(start_lnum + dir)
1530 			    || (!start_is_white
1531 				    && startPS(start_lnum + (dir > 0
1532 							     ? 1 : 0), 0, 0)))
1533 			break;
1534 		    start_lnum += dir;
1535 		}
1536 		if (!include)
1537 		    break;
1538 		if (start_lnum == (dir == BACKWARD
1539 					    ? 1 : curbuf->b_ml.ml_line_count))
1540 		    break;
1541 		prev_start_is_white = start_is_white;
1542 	    }
1543 	}
1544 	curwin->w_cursor.lnum = start_lnum;
1545 	curwin->w_cursor.col = 0;
1546 	return retval;
1547     }
1548 
1549     /*
1550      * First move back to the start_lnum of the paragraph or white lines
1551      */
1552     white_in_front = linewhite(start_lnum);
1553     while (start_lnum > 1)
1554     {
1555 	if (white_in_front)	    // stop at first white line
1556 	{
1557 	    if (!linewhite(start_lnum - 1))
1558 		break;
1559 	}
1560 	else		// stop at first non-white line of start of paragraph
1561 	{
1562 	    if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0))
1563 		break;
1564 	}
1565 	--start_lnum;
1566     }
1567 
1568     /*
1569      * Move past the end of any white lines.
1570      */
1571     end_lnum = start_lnum;
1572     while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum))
1573 	++end_lnum;
1574 
1575     --end_lnum;
1576     i = count;
1577     if (!include && white_in_front)
1578 	--i;
1579     while (i--)
1580     {
1581 	if (end_lnum == curbuf->b_ml.ml_line_count)
1582 	    return FAIL;
1583 
1584 	if (!include)
1585 	    do_white = linewhite(end_lnum + 1);
1586 
1587 	if (include || !do_white)
1588 	{
1589 	    ++end_lnum;
1590 	    /*
1591 	     * skip to end of paragraph
1592 	     */
1593 	    while (end_lnum < curbuf->b_ml.ml_line_count
1594 		    && !linewhite(end_lnum + 1)
1595 		    && !startPS(end_lnum + 1, 0, 0))
1596 		++end_lnum;
1597 	}
1598 
1599 	if (i == 0 && white_in_front && include)
1600 	    break;
1601 
1602 	/*
1603 	 * skip to end of white lines after paragraph
1604 	 */
1605 	if (include || do_white)
1606 	    while (end_lnum < curbuf->b_ml.ml_line_count
1607 						   && linewhite(end_lnum + 1))
1608 		++end_lnum;
1609     }
1610 
1611     /*
1612      * If there are no empty lines at the end, try to find some empty lines at
1613      * the start (unless that has been done already).
1614      */
1615     if (!white_in_front && !linewhite(end_lnum) && include)
1616 	while (start_lnum > 1 && linewhite(start_lnum - 1))
1617 	    --start_lnum;
1618 
1619     if (VIsual_active)
1620     {
1621 	// Problem: when doing "Vipipip" nothing happens in a single white
1622 	// line, we get stuck there.  Trap this here.
1623 	if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum)
1624 	    goto extend;
1625 	if (VIsual.lnum != start_lnum)
1626 	{
1627 	    VIsual.lnum = start_lnum;
1628 	    VIsual.col = 0;
1629 	}
1630 	VIsual_mode = 'V';
1631 	redraw_curbuf_later(INVERTED);	// update the inversion
1632 	showmode();
1633     }
1634     else
1635     {
1636 	oap->start.lnum = start_lnum;
1637 	oap->start.col = 0;
1638 	oap->motion_type = MLINE;
1639     }
1640     curwin->w_cursor.lnum = end_lnum;
1641     curwin->w_cursor.col = 0;
1642 
1643     return OK;
1644 }
1645 
1646 /*
1647  * Search quote char from string line[col].
1648  * Quote character escaped by one of the characters in "escape" is not counted
1649  * as a quote.
1650  * Returns column number of "quotechar" or -1 when not found.
1651  */
1652     static int
1653 find_next_quote(
1654     char_u	*line,
1655     int		col,
1656     int		quotechar,
1657     char_u	*escape)	// escape characters, can be NULL
1658 {
1659     int		c;
1660 
1661     for (;;)
1662     {
1663 	c = line[col];
1664 	if (c == NUL)
1665 	    return -1;
1666 	else if (escape != NULL && vim_strchr(escape, c))
1667 	    ++col;
1668 	else if (c == quotechar)
1669 	    break;
1670 	if (has_mbyte)
1671 	    col += (*mb_ptr2len)(line + col);
1672 	else
1673 	    ++col;
1674     }
1675     return col;
1676 }
1677 
1678 /*
1679  * Search backwards in "line" from column "col_start" to find "quotechar".
1680  * Quote character escaped by one of the characters in "escape" is not counted
1681  * as a quote.
1682  * Return the found column or zero.
1683  */
1684     static int
1685 find_prev_quote(
1686     char_u	*line,
1687     int		col_start,
1688     int		quotechar,
1689     char_u	*escape)	// escape characters, can be NULL
1690 {
1691     int		n;
1692 
1693     while (col_start > 0)
1694     {
1695 	--col_start;
1696 	col_start -= (*mb_head_off)(line, line + col_start);
1697 	n = 0;
1698 	if (escape != NULL)
1699 	    while (col_start - n > 0 && vim_strchr(escape,
1700 					     line[col_start - n - 1]) != NULL)
1701 	    ++n;
1702 	if (n & 1)
1703 	    col_start -= n;	// uneven number of escape chars, skip it
1704 	else if (line[col_start] == quotechar)
1705 	    break;
1706     }
1707     return col_start;
1708 }
1709 
1710 /*
1711  * Find quote under the cursor, cursor at end.
1712  * Returns TRUE if found, else FALSE.
1713  */
1714     int
1715 current_quote(
1716     oparg_T	*oap,
1717     long	count,
1718     int		include,	// TRUE == include quote char
1719     int		quotechar)	// Quote character
1720 {
1721     char_u	*line = ml_get_curline();
1722     int		col_end;
1723     int		col_start = curwin->w_cursor.col;
1724     int		inclusive = FALSE;
1725     int		vis_empty = TRUE;	// Visual selection <= 1 char
1726     int		vis_bef_curs = FALSE;	// Visual starts before cursor
1727     int		did_exclusive_adj = FALSE;  // adjusted pos for 'selection'
1728     int		inside_quotes = FALSE;	// Looks like "i'" done before
1729     int		selected_quote = FALSE;	// Has quote inside selection
1730     int		i;
1731     int		restore_vis_bef = FALSE; // restore VIsual on abort
1732 
1733     // When 'selection' is "exclusive" move the cursor to where it would be
1734     // with 'selection' "inclusive", so that the logic is the same for both.
1735     // The cursor then is moved forward after adjusting the area.
1736     if (VIsual_active)
1737     {
1738 	// this only works within one line
1739 	if (VIsual.lnum != curwin->w_cursor.lnum)
1740 	    return FALSE;
1741 
1742 	vis_bef_curs = LT_POS(VIsual, curwin->w_cursor);
1743 	vis_empty = EQUAL_POS(VIsual, curwin->w_cursor);
1744 	if (*p_sel == 'e')
1745 	{
1746 	    if (vis_bef_curs)
1747 	    {
1748 		dec_cursor();
1749 		did_exclusive_adj = TRUE;
1750 	    }
1751 	    else if (!vis_empty)
1752 	    {
1753 		dec(&VIsual);
1754 		did_exclusive_adj = TRUE;
1755 	    }
1756 	    vis_empty = EQUAL_POS(VIsual, curwin->w_cursor);
1757 	    if (!vis_bef_curs && !vis_empty)
1758 	    {
1759 		// VIsual needs to be the start of Visual selection.
1760 		pos_T t = curwin->w_cursor;
1761 
1762 		curwin->w_cursor = VIsual;
1763 		VIsual = t;
1764 		vis_bef_curs = TRUE;
1765 		restore_vis_bef = TRUE;
1766 	    }
1767 	}
1768     }
1769 
1770     if (!vis_empty)
1771     {
1772 	// Check if the existing selection exactly spans the text inside
1773 	// quotes.
1774 	if (vis_bef_curs)
1775 	{
1776 	    inside_quotes = VIsual.col > 0
1777 			&& line[VIsual.col - 1] == quotechar
1778 			&& line[curwin->w_cursor.col] != NUL
1779 			&& line[curwin->w_cursor.col + 1] == quotechar;
1780 	    i = VIsual.col;
1781 	    col_end = curwin->w_cursor.col;
1782 	}
1783 	else
1784 	{
1785 	    inside_quotes = curwin->w_cursor.col > 0
1786 			&& line[curwin->w_cursor.col - 1] == quotechar
1787 			&& line[VIsual.col] != NUL
1788 			&& line[VIsual.col + 1] == quotechar;
1789 	    i = curwin->w_cursor.col;
1790 	    col_end = VIsual.col;
1791 	}
1792 
1793 	// Find out if we have a quote in the selection.
1794 	while (i <= col_end)
1795 	    if (line[i++] == quotechar)
1796 	    {
1797 		selected_quote = TRUE;
1798 		break;
1799 	    }
1800     }
1801 
1802     if (!vis_empty && line[col_start] == quotechar)
1803     {
1804 	// Already selecting something and on a quote character.  Find the
1805 	// next quoted string.
1806 	if (vis_bef_curs)
1807 	{
1808 	    // Assume we are on a closing quote: move to after the next
1809 	    // opening quote.
1810 	    col_start = find_next_quote(line, col_start + 1, quotechar, NULL);
1811 	    if (col_start < 0)
1812 		goto abort_search;
1813 	    col_end = find_next_quote(line, col_start + 1, quotechar,
1814 							      curbuf->b_p_qe);
1815 	    if (col_end < 0)
1816 	    {
1817 		// We were on a starting quote perhaps?
1818 		col_end = col_start;
1819 		col_start = curwin->w_cursor.col;
1820 	    }
1821 	}
1822 	else
1823 	{
1824 	    col_end = find_prev_quote(line, col_start, quotechar, NULL);
1825 	    if (line[col_end] != quotechar)
1826 		goto abort_search;
1827 	    col_start = find_prev_quote(line, col_end, quotechar,
1828 							      curbuf->b_p_qe);
1829 	    if (line[col_start] != quotechar)
1830 	    {
1831 		// We were on an ending quote perhaps?
1832 		col_start = col_end;
1833 		col_end = curwin->w_cursor.col;
1834 	    }
1835 	}
1836     }
1837     else
1838 
1839     if (line[col_start] == quotechar || !vis_empty)
1840     {
1841 	int	first_col = col_start;
1842 
1843 	if (!vis_empty)
1844 	{
1845 	    if (vis_bef_curs)
1846 		first_col = find_next_quote(line, col_start, quotechar, NULL);
1847 	    else
1848 		first_col = find_prev_quote(line, col_start, quotechar, NULL);
1849 	}
1850 
1851 	// The cursor is on a quote, we don't know if it's the opening or
1852 	// closing quote.  Search from the start of the line to find out.
1853 	// Also do this when there is a Visual area, a' may leave the cursor
1854 	// in between two strings.
1855 	col_start = 0;
1856 	for (;;)
1857 	{
1858 	    // Find open quote character.
1859 	    col_start = find_next_quote(line, col_start, quotechar, NULL);
1860 	    if (col_start < 0 || col_start > first_col)
1861 		goto abort_search;
1862 	    // Find close quote character.
1863 	    col_end = find_next_quote(line, col_start + 1, quotechar,
1864 							      curbuf->b_p_qe);
1865 	    if (col_end < 0)
1866 		goto abort_search;
1867 	    // If is cursor between start and end quote character, it is
1868 	    // target text object.
1869 	    if (col_start <= first_col && first_col <= col_end)
1870 		break;
1871 	    col_start = col_end + 1;
1872 	}
1873     }
1874     else
1875     {
1876 	// Search backward for a starting quote.
1877 	col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe);
1878 	if (line[col_start] != quotechar)
1879 	{
1880 	    // No quote before the cursor, look after the cursor.
1881 	    col_start = find_next_quote(line, col_start, quotechar, NULL);
1882 	    if (col_start < 0)
1883 		goto abort_search;
1884 	}
1885 
1886 	// Find close quote character.
1887 	col_end = find_next_quote(line, col_start + 1, quotechar,
1888 							      curbuf->b_p_qe);
1889 	if (col_end < 0)
1890 	    goto abort_search;
1891     }
1892 
1893     // When "include" is TRUE, include spaces after closing quote or before
1894     // the starting quote.
1895     if (include)
1896     {
1897 	if (VIM_ISWHITE(line[col_end + 1]))
1898 	    while (VIM_ISWHITE(line[col_end + 1]))
1899 		++col_end;
1900 	else
1901 	    while (col_start > 0 && VIM_ISWHITE(line[col_start - 1]))
1902 		--col_start;
1903     }
1904 
1905     // Set start position.  After vi" another i" must include the ".
1906     // For v2i" include the quotes.
1907     if (!include && count < 2 && (vis_empty || !inside_quotes))
1908 	++col_start;
1909     curwin->w_cursor.col = col_start;
1910     if (VIsual_active)
1911     {
1912 	// Set the start of the Visual area when the Visual area was empty, we
1913 	// were just inside quotes or the Visual area didn't start at a quote
1914 	// and didn't include a quote.
1915 	if (vis_empty
1916 		|| (vis_bef_curs
1917 		    && !selected_quote
1918 		    && (inside_quotes
1919 			|| (line[VIsual.col] != quotechar
1920 			    && (VIsual.col == 0
1921 				|| line[VIsual.col - 1] != quotechar)))))
1922 	{
1923 	    VIsual = curwin->w_cursor;
1924 	    redraw_curbuf_later(INVERTED);
1925 	}
1926     }
1927     else
1928     {
1929 	oap->start = curwin->w_cursor;
1930 	oap->motion_type = MCHAR;
1931     }
1932 
1933     // Set end position.
1934     curwin->w_cursor.col = col_end;
1935     if ((include || count > 1 // After vi" another i" must include the ".
1936 		|| (!vis_empty && inside_quotes)
1937 	) && inc_cursor() == 2)
1938 	inclusive = TRUE;
1939     if (VIsual_active)
1940     {
1941 	if (vis_empty || vis_bef_curs)
1942 	{
1943 	    // decrement cursor when 'selection' is not exclusive
1944 	    if (*p_sel != 'e')
1945 		dec_cursor();
1946 	}
1947 	else
1948 	{
1949 	    // Cursor is at start of Visual area.  Set the end of the Visual
1950 	    // area when it was just inside quotes or it didn't end at a
1951 	    // quote.
1952 	    if (inside_quotes
1953 		    || (!selected_quote
1954 			&& line[VIsual.col] != quotechar
1955 			&& (line[VIsual.col] == NUL
1956 			    || line[VIsual.col + 1] != quotechar)))
1957 	    {
1958 		dec_cursor();
1959 		VIsual = curwin->w_cursor;
1960 	    }
1961 	    curwin->w_cursor.col = col_start;
1962 	}
1963 	if (VIsual_mode == 'V')
1964 	{
1965 	    VIsual_mode = 'v';
1966 	    redraw_cmdline = TRUE;		// show mode later
1967 	}
1968     }
1969     else
1970     {
1971 	// Set inclusive and other oap's flags.
1972 	oap->inclusive = inclusive;
1973     }
1974 
1975     return OK;
1976 
1977 abort_search:
1978     if (VIsual_active && *p_sel == 'e')
1979     {
1980 	if (did_exclusive_adj)
1981 	    inc_cursor();
1982 	if (restore_vis_bef)
1983 	{
1984 	    pos_T t = curwin->w_cursor;
1985 
1986 	    curwin->w_cursor = VIsual;
1987 	    VIsual = t;
1988 	}
1989     }
1990     return FALSE;
1991 }
1992 
1993 #endif // FEAT_TEXTOBJ
1994