Annotation of src/usr.bin/vim/search.c, Revision 1.1.1.1
1.1 downsj 1: /* $OpenBSD$ */
2: /* vi:set ts=4 sw=4:
3: *
4: * VIM - Vi IMproved by Bram Moolenaar
5: *
6: * Do ":help uganda" in Vim to read copying and usage conditions.
7: * Do ":help credits" in Vim to see a list of people who contributed.
8: */
9: /*
10: * search.c: code for normal mode searching commands
11: */
12:
13: #include "vim.h"
14: #include "globals.h"
15: #include "proto.h"
16: #include "option.h"
17: #include "ops.h" /* for op_inclusive */
18:
19: /* modified Henry Spencer's regular expression routines */
20: #include "regexp.h"
21:
22: static int inmacro __ARGS((char_u *, char_u *));
23: static int cls __ARGS((void));
24: static int skip_chars __ARGS((int, int));
25: static void back_in_line __ARGS((void));
26: static void find_first_blank __ARGS((FPOS *));
27: static void show_pat_in_path __ARGS((char_u *, int,
28: int, int, FILE *, linenr_t *, long));
29: static void give_warning __ARGS((char_u *));
30:
31: static char_u *top_bot_msg = (char_u *)"search hit TOP, continuing at BOTTOM";
32: static char_u *bot_top_msg = (char_u *)"search hit BOTTOM, continuing at TOP";
33:
34: /*
35: * This file contains various searching-related routines. These fall into
36: * three groups:
37: * 1. string searches (for /, ?, n, and N)
38: * 2. character searches within a single line (for f, F, t, T, etc)
39: * 3. "other" kinds of searches like the '%' command, and 'word' searches.
40: */
41:
42: /*
43: * String searches
44: *
45: * The string search functions are divided into two levels:
46: * lowest: searchit(); called by do_search() and edit().
47: * Highest: do_search(); changes curwin->w_cursor, called by normal().
48: *
49: * The last search pattern is remembered for repeating the same search.
50: * This pattern is shared between the :g, :s, ? and / commands.
51: * This is in myregcomp().
52: *
53: * The actual string matching is done using a heavily modified version of
54: * Henry Spencer's regular expression library.
55: */
56:
57: /*
58: * Two search patterns are remembered: One for the :substitute command and
59: * one for other searches. last_pattern points to the one that was
60: * used the last time.
61: */
62: static char_u *search_pattern = NULL;
63: static int search_magic = TRUE;
64: static int search_no_scs = FALSE;
65: static char_u *subst_pattern = NULL;
66: static int subst_magic = TRUE;
67: static int subst_no_scs = FALSE;
68: static char_u *last_pattern = NULL;
69: static int last_magic = TRUE;
70: static int last_no_scs = FALSE;
71: static char_u *mr_pattern = NULL; /* pattern used by myregcomp() */
72:
73: static int want_start; /* looking for start of line? */
74:
75: /*
76: * Type used by find_pattern_in_path() to remember which included files have
77: * been searched already.
78: */
79: typedef struct SearchedFile
80: {
81: FILE *fp; /* File pointer */
82: char_u *name; /* Full name of file */
83: linenr_t lnum; /* Line we were up to in file */
84: int matched; /* Found a match in this file */
85: } SearchedFile;
86:
87: /*
88: * translate search pattern for vim_regcomp()
89: *
90: * sub_cmd == RE_SEARCH: save pat in search_pattern (normal search command)
91: * sub_cmd == RE_SUBST: save pat in subst_pattern (:substitute command)
92: * sub_cmd == RE_BOTH: save pat in both patterns (:global command)
93: * which_pat == RE_SEARCH: use previous search pattern if "pat" is NULL
94: * which_pat == RE_SUBST: use previous sustitute pattern if "pat" is NULL
95: * which_pat == RE_LAST: use last used pattern if "pat" is NULL
96: * options & SEARCH_HIS: put search string in history
97: * options & SEARCH_KEEP: keep previous search pattern
98: *
99: */
100: regexp *
101: myregcomp(pat, sub_cmd, which_pat, options)
102: char_u *pat;
103: int sub_cmd;
104: int which_pat;
105: int options;
106: {
107: rc_did_emsg = FALSE;
108: reg_magic = p_magic;
109: if (pat == NULL || *pat == NUL) /* use previous search pattern */
110: {
111: if (which_pat == RE_SEARCH)
112: {
113: if (search_pattern == NULL)
114: {
115: emsg(e_noprevre);
116: rc_did_emsg = TRUE;
117: return (regexp *) NULL;
118: }
119: pat = search_pattern;
120: reg_magic = search_magic;
121: no_smartcase = search_no_scs;
122: }
123: else if (which_pat == RE_SUBST)
124: {
125: if (subst_pattern == NULL)
126: {
127: emsg(e_nopresub);
128: rc_did_emsg = TRUE;
129: return (regexp *) NULL;
130: }
131: pat = subst_pattern;
132: reg_magic = subst_magic;
133: no_smartcase = subst_no_scs;
134: }
135: else /* which_pat == RE_LAST */
136: {
137: if (last_pattern == NULL)
138: {
139: emsg(e_noprevre);
140: rc_did_emsg = TRUE;
141: return (regexp *) NULL;
142: }
143: pat = last_pattern;
144: reg_magic = last_magic;
145: no_smartcase = last_no_scs;
146: }
147: }
148: else if (options & SEARCH_HIS)
149: add_to_history(1, pat); /* put new pattern in history */
150:
151: mr_pattern = pat;
152:
153: /*
154: * save the currently used pattern in the appropriate place,
155: * unless the pattern should not be remembered
156: */
157: if (!(options & SEARCH_KEEP))
158: {
159: /*
160: * search or global command
161: */
162: if (sub_cmd == RE_SEARCH || sub_cmd == RE_BOTH)
163: {
164: if (search_pattern != pat)
165: {
166: vim_free(search_pattern);
167: search_pattern = strsave(pat);
168: last_pattern = search_pattern;
169: search_magic = reg_magic;
170: last_magic = reg_magic; /* Magic sticks with the r.e. */
171: search_no_scs = no_smartcase;
172: last_no_scs = no_smartcase;
173: }
174: }
175: /*
176: * substitute or global command
177: */
178: if (sub_cmd == RE_SUBST || sub_cmd == RE_BOTH)
179: {
180: if (subst_pattern != pat)
181: {
182: vim_free(subst_pattern);
183: subst_pattern = strsave(pat);
184: last_pattern = subst_pattern;
185: subst_magic = reg_magic;
186: last_magic = reg_magic; /* Magic sticks with the r.e. */
187: subst_no_scs = no_smartcase;
188: last_no_scs = no_smartcase;
189: }
190: }
191: }
192:
193: want_start = (*pat == '^'); /* looking for start of line? */
194: set_reg_ic(pat); /* tell the vim_regexec routine how to search */
195: return vim_regcomp(pat);
196: }
197:
198: /*
199: * Set reg_ic according to p_ic, p_scs and the search pattern.
200: */
201: void
202: set_reg_ic(pat)
203: char_u *pat;
204: {
205: char_u *p;
206:
207: reg_ic = p_ic;
208: if (!no_smartcase && p_scs
209: #ifdef INSERT_EXPAND
210: && !(ctrl_x_mode && curbuf->b_p_inf)
211: #endif
212: )
213: {
214: /* don't ignore case if pattern has uppercase */
215: for (p = pat; *p; )
216: if (isupper(*p++))
217: reg_ic = FALSE;
218: }
219: no_smartcase = FALSE;
220: }
221:
222: /*
223: * lowest level search function.
224: * Search for 'count'th occurrence of 'str' in direction 'dir'.
225: * Start at position 'pos' and return the found position in 'pos'.
226: *
227: * if (options & SEARCH_MSG) == 0 don't give any messages
228: * if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages
229: * if (options & SEARCH_MSG) == SEARCH_MSG give all messages
230: * if (options & SEARCH_HIS) put search pattern in history
231: * if (options & SEARCH_END) return position at end of match
232: * if (options & SEARCH_START) accept match at pos itself
233: * if (options & SEARCH_KEEP) keep previous search pattern
234: *
235: * Return OK for success, FAIL for failure.
236: */
237: int
238: searchit(pos, dir, str, count, options, which_pat)
239: FPOS *pos;
240: int dir;
241: char_u *str;
242: long count;
243: int options;
244: int which_pat;
245: {
246: int found;
247: linenr_t lnum; /* no init to shut up Apollo cc */
248: regexp *prog;
249: char_u *ptr;
250: register char_u *match = NULL, *matchend = NULL; /* init for GCC */
251: int loop;
252: FPOS start_pos;
253: int at_first_line;
254: int extra_col;
255: int match_ok;
256: char_u *p;
257:
258: if ((prog = myregcomp(str, RE_SEARCH, which_pat,
259: (options & (SEARCH_HIS + SEARCH_KEEP)))) == NULL)
260: {
261: if ((options & SEARCH_MSG) && !rc_did_emsg)
262: emsg2((char_u *)"Invalid search string: %s", mr_pattern);
263: return FAIL;
264: }
265:
266: if (options & SEARCH_START)
267: extra_col = 0;
268: else
269: extra_col = 1;
270:
271:
272: /*
273: * find the string
274: */
275: do /* loop for count */
276: {
277: start_pos = *pos; /* remember start pos for detecting no match */
278: found = 0; /* default: not found */
279:
280: /*
281: * Start searching in current line, unless searching backwards and
282: * we're in column 0 or searching forward and we're past the end of
283: * the line
284: */
285: if (dir == BACKWARD && start_pos.col == 0)
286: {
287: lnum = pos->lnum - 1;
288: at_first_line = FALSE;
289: }
290: else
291: {
292: lnum = pos->lnum;
293: at_first_line = TRUE;
294: }
295:
296: for (loop = 0; loop <= 1; ++loop) /* loop twice if 'wrapscan' set */
297: {
298: for ( ; lnum > 0 && lnum <= curbuf->b_ml.ml_line_count;
299: lnum += dir, at_first_line = FALSE)
300: {
301: ptr = ml_get(lnum);
302: /* forward search, first line */
303: if (dir == FORWARD && at_first_line && start_pos.col > 0 &&
304: want_start)
305: continue; /* match not possible */
306:
307: /*
308: * Look for a match somewhere in the line.
309: */
310: if (vim_regexec(prog, ptr, TRUE))
311: {
312: match = prog->startp[0];
313: matchend = prog->endp[0];
314:
315: /*
316: * Forward search in the first line: match should be after
317: * the start position. If not, continue at the end of the
318: * match (this is vi compatible).
319: */
320: if (dir == FORWARD && at_first_line)
321: {
322: match_ok = TRUE;
323: /*
324: * When *match == NUL the cursor will be put one back
325: * afterwards, compare with that position, otherwise
326: * "/$" will get stuck on end of line. Same for
327: * matchend.
328: */
329: while ((options & SEARCH_END) ?
330: ((int)(matchend - ptr) - 1 - (int)(*matchend == NUL) <
331: (int)start_pos.col + extra_col) :
332: ((int)(match - ptr) - (int)(*match == NUL) <
333: (int)start_pos.col + extra_col))
334: {
335: /*
336: * If vi-compatible searching, continue at the end
337: * of the match, otherwise continue one position
338: * forward.
339: */
340: if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
341: {
342: p = matchend;
343: if (match == p && *p != NUL)
344: ++p;
345: }
346: else
347: {
348: p = match;
349: if (*p != NUL)
350: ++p;
351: }
352: if (*p != NUL && vim_regexec(prog, p, FALSE))
353: {
354: match = prog->startp[0];
355: matchend = prog->endp[0];
356: }
357: else
358: {
359: match_ok = FALSE;
360: break;
361: }
362: }
363: if (!match_ok)
364: continue;
365: }
366: if (dir == BACKWARD && !want_start)
367: {
368: /*
369: * Now, if there are multiple matches on this line,
370: * we have to get the last one. Or the last one before
371: * the cursor, if we're on that line.
372: * When putting the new cursor at the end, compare
373: * relative to the end of the match.
374: */
375: match_ok = FALSE;
376: for (;;)
377: {
378: if (!at_first_line || ((options & SEARCH_END) ?
379: ((prog->endp[0] - ptr) - 1 + extra_col
380: <= (int)start_pos.col) :
381: ((prog->startp[0] - ptr) + extra_col
382: <= (int)start_pos.col)))
383: {
384: match_ok = TRUE;
385: match = prog->startp[0];
386: matchend = prog->endp[0];
387: }
388: else
389: break;
390: /*
391: * If vi-compatible searching, continue at the end
392: * of the match, otherwise continue one position
393: * forward.
394: */
395: if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
396: {
397: p = matchend;
398: if (p == match && *p != NUL)
399: ++p;
400: }
401: else
402: {
403: p = match;
404: if (*p != NUL)
405: ++p;
406: }
407: if (*p == NUL || !vim_regexec(prog, p, (int)FALSE))
408: break;
409: }
410:
411: /*
412: * If there is only a match after the cursor, skip
413: * this match.
414: */
415: if (!match_ok)
416: continue;
417: }
418:
419: pos->lnum = lnum;
420: if (options & SEARCH_END && !(options & SEARCH_NOOF))
421: pos->col = (int) (matchend - ptr - 1);
422: else
423: pos->col = (int) (match - ptr);
424: found = 1;
425: break;
426: }
427: line_breakcheck(); /* stop if ctrl-C typed */
428: if (got_int)
429: break;
430:
431: if (loop && lnum == start_pos.lnum)
432: break; /* if second loop, stop where started */
433: }
434: at_first_line = FALSE;
435:
436: /*
437: * stop the search if wrapscan isn't set, after an interrupt and
438: * after a match
439: */
440: if (!p_ws || got_int || found)
441: break;
442:
443: /*
444: * If 'wrapscan' is set we continue at the other end of the file.
445: * If 'shortmess' does not contain 's', we give a message.
446: * This message is also remembered in keep_msg for when the screen
447: * is redrawn. The keep_msg is cleared whenever another message is
448: * written.
449: */
450: if (dir == BACKWARD) /* start second loop at the other end */
451: {
452: lnum = curbuf->b_ml.ml_line_count;
453: if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
454: give_warning(top_bot_msg);
455: }
456: else
457: {
458: lnum = 1;
459: if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
460: give_warning(bot_top_msg);
461: }
462: }
463: if (got_int)
464: break;
465: }
466: while (--count > 0 && found); /* stop after count matches or no match */
467:
468: vim_free(prog);
469:
470: if (!found) /* did not find it */
471: {
472: if (got_int)
473: emsg(e_interr);
474: else if ((options & SEARCH_MSG) == SEARCH_MSG)
475: {
476: if (p_ws)
477: EMSG2("Pattern not found: %s", mr_pattern);
478: else if (lnum == 0)
479: EMSG2("search hit TOP without match for: %s", mr_pattern);
480: else
481: EMSG2("search hit BOTTOM without match for: %s", mr_pattern);
482: }
483: return FAIL;
484: }
485: search_match_len = matchend - match;
486:
487: return OK;
488: }
489:
490: /*
491: * Highest level string search function.
492: * Search for the 'count'th occurence of string 'str' in direction 'dirc'
493: * If 'dirc' is 0: use previous dir.
494: * If 'str' is NULL or empty : use previous string.
495: * If 'options & SEARCH_REV' : go in reverse of previous dir.
496: * If 'options & SEARCH_ECHO': echo the search command and handle options
497: * If 'options & SEARCH_MSG' : may give error message
498: * If 'options & SEARCH_OPT' : interpret optional flags
499: * If 'options & SEARCH_HIS' : put search pattern in history
500: * If 'options & SEARCH_NOOF': don't add offset to position
501: * If 'options & SEARCH_MARK': set previous context mark
502: * If 'options & SEARCH_KEEP': keep previous search pattern
503: *
504: * Careful: If lastoffline == TRUE and lastoff == 0 this makes the
505: * movement linewise without moving the match position.
506: *
507: * return 0 for failure, 1 for found, 2 for found and line offset added
508: */
509: int
510: do_search(dirc, str, count, options)
511: int dirc;
512: char_u *str;
513: long count;
514: int options;
515: {
516: FPOS pos; /* position of the last match */
517: char_u *searchstr;
518: static int lastsdir = '/'; /* previous search direction */
519: static int lastoffline;/* previous/current search has line offset */
520: static int lastend; /* previous/current search set cursor at end */
521: static long lastoff; /* previous/current line or char offset */
522: int old_lastsdir;
523: int old_lastoffline;
524: int old_lastend;
525: long old_lastoff;
526: int retval; /* Return value */
527: register char_u *p;
528: register long c;
529: char_u *dircp;
530: int i = 0; /* init for GCC */
531:
532: /*
533: * A line offset is not remembered, this is vi compatible.
534: */
535: if (lastoffline && vim_strchr(p_cpo, CPO_LINEOFF) != NULL)
536: {
537: lastoffline = FALSE;
538: lastoff = 0;
539: }
540:
541: /*
542: * Save the values for when (options & SEARCH_KEEP) is used.
543: * (there is no "if ()" around this because gcc wants them initialized)
544: */
545: old_lastsdir = lastsdir;
546: old_lastoffline = lastoffline;
547: old_lastend = lastend;
548: old_lastoff = lastoff;
549:
550: pos = curwin->w_cursor; /* start searching at the cursor position */
551:
552: /*
553: * Find out the direction of the search.
554: */
555: if (dirc == 0)
556: dirc = lastsdir;
557: else
558: lastsdir = dirc;
559: if (options & SEARCH_REV)
560: {
561: #ifdef WIN32
562: /* There is a bug in the Visual C++ 2.2 compiler which means that
563: * dirc always ends up being '/' */
564: dirc = (dirc == '/') ? '?' : '/';
565: #else
566: if (dirc == '/')
567: dirc = '?';
568: else
569: dirc = '/';
570: #endif
571: }
572:
573: /*
574: * Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
575: */
576: for (;;)
577: {
578: searchstr = str;
579: dircp = NULL;
580: /* use previous pattern */
581: if (str == NULL || *str == NUL || *str == dirc)
582: {
583: if (search_pattern == NULL) /* no previous pattern */
584: {
585: emsg(e_noprevre);
586: retval = 0;
587: goto end_do_search;
588: }
589: searchstr = (char_u *)""; /* make myregcomp() use search_pattern */
590: }
591:
592: if (str != NULL && *str != NUL) /* look for (new) offset */
593: {
594: /*
595: * Find end of regular expression.
596: * If there is a matching '/' or '?', toss it.
597: */
598: p = skip_regexp(str, dirc);
599: if (*p == dirc)
600: {
601: dircp = p; /* remember where we put the NUL */
602: *p++ = NUL;
603: }
604: lastoffline = FALSE;
605: lastend = FALSE;
606: lastoff = 0;
607: /*
608: * Check for a line offset or a character offset.
609: * For get_address (echo off) we don't check for a character
610: * offset, because it is meaningless and the 's' could be a
611: * substitute command.
612: */
613: if (*p == '+' || *p == '-' || isdigit(*p))
614: lastoffline = TRUE;
615: else if ((options & SEARCH_OPT) &&
616: (*p == 'e' || *p == 's' || *p == 'b'))
617: {
618: if (*p == 'e') /* end */
619: lastend = SEARCH_END;
620: ++p;
621: }
622: if (isdigit(*p) || *p == '+' || *p == '-') /* got an offset */
623: {
624: if (isdigit(*p) || isdigit(*(p + 1)))
625: lastoff = atol((char *)p); /* 'nr' or '+nr' or '-nr' */
626: else if (*p == '-') /* single '-' */
627: lastoff = -1;
628: else /* single '+' */
629: lastoff = 1;
630: ++p;
631: while (isdigit(*p)) /* skip number */
632: ++p;
633: }
634: searchcmdlen = p - str; /* compute length of search command
635: for get_address() */
636: str = p; /* put str after search command */
637: }
638:
639: if (options & SEARCH_ECHO)
640: {
641: msg_start();
642: msg_outchar(dirc);
643: msg_outtrans(*searchstr == NUL ? search_pattern : searchstr);
644: if (lastoffline || lastend || lastoff)
645: {
646: msg_outchar(dirc);
647: if (lastend)
648: msg_outchar('e');
649: else if (!lastoffline)
650: msg_outchar('s');
651: if (lastoff < 0)
652: {
653: msg_outchar('-');
654: msg_outnum((long)-lastoff);
655: }
656: else if (lastoff > 0 || lastoffline)
657: {
658: msg_outchar('+');
659: msg_outnum((long)lastoff);
660: }
661: }
662: msg_clr_eos();
663: (void)msg_check();
664:
665: gotocmdline(FALSE);
666: flushbuf();
667: }
668:
669: /*
670: * If there is a character offset, subtract it from the current
671: * position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
672: * This is not done for a line offset, because then we would not be vi
673: * compatible.
674: */
675: if (!lastoffline && lastoff)
676: {
677: if (lastoff > 0)
678: {
679: c = lastoff;
680: while (c--)
681: if ((i = dec(&pos)) != 0)
682: break;
683: if (i == -1) /* at start of buffer */
684: goto_endofbuf(&pos);
685: }
686: else
687: {
688: c = -lastoff;
689: while (c--)
690: if ((i = inc(&pos)) != 0)
691: break;
692: if (i == -1) /* at end of buffer */
693: {
694: pos.lnum = 1;
695: pos.col = 0;
696: }
697: }
698: }
699:
700: c = searchit(&pos, dirc == '/' ? FORWARD : BACKWARD, searchstr, count,
701: lastend + (options &
702: (SEARCH_KEEP + SEARCH_HIS + SEARCH_MSG +
703: ((str != NULL && *str == ';') ? 0 : SEARCH_NOOF))),
704: 2);
705: if (dircp != NULL)
706: *dircp = dirc; /* put second '/' or '?' back for normal() */
707: if (c == FAIL)
708: {
709: retval = 0;
710: goto end_do_search;
711: }
712: if (lastend)
713: op_inclusive = TRUE; /* 'e' includes last character */
714:
715: retval = 1; /* pattern found */
716:
717: /*
718: * Add character and/or line offset
719: */
720: if (!(options & SEARCH_NOOF) || *str == ';')
721: {
722: if (lastoffline) /* Add the offset to the line number. */
723: {
724: c = pos.lnum + lastoff;
725: if (c < 1)
726: pos.lnum = 1;
727: else if (c > curbuf->b_ml.ml_line_count)
728: pos.lnum = curbuf->b_ml.ml_line_count;
729: else
730: pos.lnum = c;
731: pos.col = 0;
732:
733: retval = 2; /* pattern found, line offset added */
734: }
735: else
736: {
737: if (lastoff > 0) /* to the right, check for end of line */
738: {
739: p = ml_get_pos(&pos) + 1;
740: c = lastoff;
741: while (c-- && *p++ != NUL)
742: ++pos.col;
743: }
744: else /* to the left, check for start of line */
745: {
746: if ((c = pos.col + lastoff) < 0)
747: c = 0;
748: pos.col = c;
749: }
750: }
751: }
752:
753: /*
754: * The search command can be followed by a ';' to do another search.
755: * For example: "/pat/;/foo/+3;?bar"
756: * This is like doing another search command, except:
757: * - The remembered direction '/' or '?' is from the first search.
758: * - When an error happens the cursor isn't moved at all.
759: * Don't do this when called by get_address() (it handles ';' itself).
760: */
761: if (!(options & SEARCH_OPT) || str == NULL || *str != ';')
762: break;
763:
764: dirc = *++str;
765: if (dirc != '?' && dirc != '/')
766: {
767: retval = 0;
768: EMSG("Expected '?' or '/' after ';'");
769: goto end_do_search;
770: }
771: ++str;
772: }
773:
774: if (options & SEARCH_MARK)
775: setpcmark();
776: curwin->w_cursor = pos;
777: curwin->w_set_curswant = TRUE;
778:
779: end_do_search:
780: if (options & SEARCH_KEEP)
781: {
782: lastsdir = old_lastsdir;
783: lastoffline = old_lastoffline;
784: lastend = old_lastend;
785: lastoff = old_lastoff;
786: }
787: return retval;
788: }
789:
790: /*
791: * search_for_exact_line(pos, dir, pat)
792: *
793: * Search for a line starting with the given pattern (ignoring leading
794: * white-space), starting from pos and going in direction dir. pos will
795: * contain the position of the match found. Blank lines will never match.
796: * Return OK for success, or FAIL if no line found.
797: */
798: int
799: search_for_exact_line(pos, dir, pat)
800: FPOS *pos;
801: int dir;
802: char_u *pat;
803: {
804: linenr_t start = 0;
805: char_u *ptr;
806: char_u *p;
807:
808: if (curbuf->b_ml.ml_line_count == 0)
809: return FAIL;
810: for (;;)
811: {
812: pos->lnum += dir;
813: if (pos->lnum < 1)
814: {
815: if (p_ws)
816: {
817: pos->lnum = curbuf->b_ml.ml_line_count;
818: if (!shortmess(SHM_SEARCH))
819: give_warning(top_bot_msg);
820: }
821: else
822: {
823: pos->lnum = 1;
824: break;
825: }
826: }
827: else if (pos->lnum > curbuf->b_ml.ml_line_count)
828: {
829: if (p_ws)
830: {
831: pos->lnum = 1;
832: if (!shortmess(SHM_SEARCH))
833: give_warning(bot_top_msg);
834: }
835: else
836: {
837: pos->lnum = 1;
838: break;
839: }
840: }
841: if (pos->lnum == start)
842: break;
843: if (start == 0)
844: start = pos->lnum;
845: ptr = ml_get(pos->lnum);
846: p = skipwhite(ptr);
847: pos->col = p - ptr;
848: if (*p != NUL && STRNCMP(p, pat, STRLEN(pat)) == 0)
849: return OK;
850: else if (*p != NUL && p_ic)
851: {
852: ptr = pat;
853: while (*p && TO_LOWER(*p) == TO_LOWER(*ptr))
854: {
855: ++p;
856: ++ptr;
857: }
858: if (*ptr == NUL)
859: return OK;
860: }
861: }
862: return FAIL;
863: }
864:
865: /*
866: * Character Searches
867: */
868:
869: /*
870: * searchc(c, dir, type, count)
871: *
872: * Search for character 'c', in direction 'dir'. If 'type' is 0, move to the
873: * position of the character, otherwise move to just before the char.
874: * Repeat this 'count' times.
875: */
876: int
877: searchc(c, dir, type, count)
878: int c;
879: register int dir;
880: int type;
881: long count;
882: {
883: static int lastc = NUL; /* last character searched for */
884: static int lastcdir; /* last direction of character search */
885: static int lastctype; /* last type of search ("find" or "to") */
886: register int col;
887: char_u *p;
888: int len;
889:
890: if (c != NUL) /* normal search: remember args for repeat */
891: {
892: if (!KeyStuffed) /* don't remember when redoing */
893: {
894: lastc = c;
895: lastcdir = dir;
896: lastctype = type;
897: }
898: }
899: else /* repeat previous search */
900: {
901: if (lastc == NUL)
902: return FALSE;
903: if (dir) /* repeat in opposite direction */
904: dir = -lastcdir;
905: else
906: dir = lastcdir;
907: type = lastctype;
908: c = lastc;
909: }
910:
911: p = ml_get_curline();
912: col = curwin->w_cursor.col;
913: len = STRLEN(p);
914:
915: while (count--)
916: {
917: for (;;)
918: {
919: if ((col += dir) < 0 || col >= len)
920: return FALSE;
921: if (p[col] == c)
922: break;
923: }
924: }
925: if (type)
926: col -= dir;
927: curwin->w_cursor.col = col;
928: return TRUE;
929: }
930:
931: /*
932: * "Other" Searches
933: */
934:
935: /*
936: * findmatch - find the matching paren or brace
937: *
938: * Improvement over vi: Braces inside quotes are ignored.
939: */
940: FPOS *
941: findmatch(initc)
942: int initc;
943: {
944: return findmatchlimit(initc, 0, 0);
945: }
946:
947: /*
948: * findmatchlimit -- find the matching paren or brace, if it exists within
949: * maxtravel lines of here. A maxtravel of 0 means search until you fall off
950: * the edge of the file.
951: *
952: * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
953: * FM_FORWARD search forwards (when initc is '/', '*' or '#')
954: * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
955: * FM_SKIPCOMM skip comments (not implemented yet!)
956: */
957:
958: FPOS *
959: findmatchlimit(initc, flags, maxtravel)
960: int initc;
961: int flags;
962: int maxtravel;
963: {
964: static FPOS pos; /* current search position */
965: int findc; /* matching brace */
966: int c;
967: int count = 0; /* cumulative number of braces */
968: int idx = 0; /* init for gcc */
969: static char_u table[6] = {'(', ')', '[', ']', '{', '}'};
970: #ifdef RIGHTLEFT
971: static char_u table_rl[6] = {')', '(', ']', '[', '}', '{'};
972: #endif
973: int inquote = FALSE; /* TRUE when inside quotes */
974: register char_u *linep; /* pointer to current line */
975: char_u *ptr;
976: int do_quotes; /* check for quotes in current line */
977: int at_start; /* do_quotes value at start position */
978: int hash_dir = 0; /* Direction searched for # things */
979: int comment_dir = 0; /* Direction searched for comments */
980: FPOS match_pos; /* Where last slash-star was found */
981: int start_in_quotes; /* start position is in quotes */
982: int traveled = 0; /* how far we've searched so far */
983: int ignore_cend = FALSE; /* ignore comment end */
984: int cpo_match; /* vi compatible matching */
985: int dir; /* Direction to search */
986:
987: pos = curwin->w_cursor;
988: linep = ml_get(pos.lnum);
989:
990: cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL);
991:
992: /* Direction to search when initc is '/', '*' or '#' */
993: if (flags & FM_BACKWARD)
994: dir = BACKWARD;
995: else if (flags & FM_FORWARD)
996: dir = FORWARD;
997: else
998: dir = 0;
999:
1000: /*
1001: * if initc given, look in the table for the matching character
1002: * '/' and '*' are special cases: look for start or end of comment.
1003: * When '/' is used, we ignore running backwards into an star-slash, for
1004: * "[*" command, we just want to find any comment.
1005: */
1006: if (initc == '/' || initc == '*')
1007: {
1008: comment_dir = dir;
1009: if (initc == '/')
1010: ignore_cend = TRUE;
1011: idx = (dir == FORWARD) ? 0 : 1;
1012: initc = NUL;
1013: }
1014: else if (initc != '#' && initc != NUL)
1015: {
1016: for (idx = 0; idx < 6; ++idx)
1017: #ifdef RIGHTLEFT
1018: if ((curwin->w_p_rl ? table_rl : table)[idx] == initc)
1019: {
1020: initc = (curwin->w_p_rl ? table_rl : table)[idx = idx ^ 1];
1021:
1022: #else
1023: if (table[idx] == initc)
1024: {
1025: initc = table[idx = idx ^ 1];
1026: #endif
1027: break;
1028: }
1029: if (idx == 6) /* invalid initc! */
1030: return NULL;
1031: }
1032: /*
1033: * Either initc is '#', or no initc was given and we need to look under the
1034: * cursor.
1035: */
1036: else
1037: {
1038: if (initc == '#')
1039: {
1040: hash_dir = dir;
1041: }
1042: else
1043: {
1044: /*
1045: * initc was not given, must look for something to match under
1046: * or near the cursor.
1047: */
1048: if (linep[0] == '#' && pos.col == 0)
1049: {
1050: /* If it's not #if, #else etc, we should look for a brace
1051: * instead */
1052: for (c = 1; vim_iswhite(linep[c]); c++)
1053: ;
1054: if (STRNCMP(linep + c, "if", (size_t)2) == 0 ||
1055: STRNCMP(linep + c, "endif", (size_t)5) == 0 ||
1056: STRNCMP(linep + c, "el", (size_t)2) == 0)
1057: hash_dir = 1;
1058: }
1059:
1060: /*
1061: * Are we on a comment?
1062: */
1063: else if (linep[pos.col] == '/')
1064: {
1065: if (linep[pos.col + 1] == '*')
1066: {
1067: comment_dir = 1;
1068: idx = 0;
1069: pos.col++;
1070: }
1071: else if (pos.col > 0 && linep[pos.col - 1] == '*')
1072: {
1073: comment_dir = -1;
1074: idx = 1;
1075: pos.col--;
1076: }
1077: }
1078: else if (linep[pos.col] == '*')
1079: {
1080: if (linep[pos.col + 1] == '/')
1081: {
1082: comment_dir = -1;
1083: idx = 1;
1084: }
1085: else if (pos.col > 0 && linep[pos.col - 1] == '/')
1086: {
1087: comment_dir = 1;
1088: idx = 0;
1089: }
1090: }
1091:
1092: /*
1093: * If we are not on a comment or the # at the start of a line, then
1094: * look for brace anywhere on this line after the cursor.
1095: */
1096: if (!hash_dir && !comment_dir)
1097: {
1098: /*
1099: * find the brace under or after the cursor
1100: */
1101: linep = ml_get(pos.lnum);
1102: idx = 6; /* error if this line is empty */
1103: for (;;)
1104: {
1105: initc = linep[pos.col];
1106: if (initc == NUL)
1107: break;
1108:
1109: for (idx = 0; idx < 6; ++idx)
1110: #ifdef RIGHTLEFT
1111: if ((curwin->w_p_rl ? table_rl : table)[idx] == initc)
1112: #else
1113: if (table[idx] == initc)
1114: #endif
1115: break;
1116: if (idx != 6)
1117: break;
1118: ++pos.col;
1119: }
1120: if (idx == 6)
1121: {
1122: if (linep[0] == '#')
1123: hash_dir = 1;
1124: else
1125: return NULL;
1126: }
1127: }
1128: }
1129: if (hash_dir)
1130: {
1131: /*
1132: * Look for matching #if, #else, #elif, or #endif
1133: */
1134: op_motion_type = MLINE; /* Linewise for this case only */
1135: if (initc != '#')
1136: {
1137: ptr = skipwhite(linep + 1);
1138: if (STRNCMP(ptr, "if", (size_t)2) == 0 ||
1139: STRNCMP(ptr, "el", (size_t)2) == 0)
1140: hash_dir = 1;
1141: else if (STRNCMP(ptr, "endif", (size_t)5) == 0)
1142: hash_dir = -1;
1143: else
1144: return NULL;
1145: }
1146: pos.col = 0;
1147: while (!got_int)
1148: {
1149: if (hash_dir > 0)
1150: {
1151: if (pos.lnum == curbuf->b_ml.ml_line_count)
1152: break;
1153: }
1154: else if (pos.lnum == 1)
1155: break;
1156: pos.lnum += hash_dir;
1157: linep = ml_get(pos.lnum);
1158: line_breakcheck();
1159: if (linep[0] != '#')
1160: continue;
1161: ptr = linep + 1;
1162: while (*ptr == ' ' || *ptr == TAB)
1163: ptr++;
1164: if (hash_dir > 0)
1165: {
1166: if (STRNCMP(ptr, "if", (size_t)2) == 0)
1167: count++;
1168: else if (STRNCMP(ptr, "el", (size_t)2) == 0)
1169: {
1170: if (count == 0)
1171: return &pos;
1172: }
1173: else if (STRNCMP(ptr, "endif", (size_t)5) == 0)
1174: {
1175: if (count == 0)
1176: return &pos;
1177: count--;
1178: }
1179: }
1180: else
1181: {
1182: if (STRNCMP(ptr, "if", (size_t)2) == 0)
1183: {
1184: if (count == 0)
1185: return &pos;
1186: count--;
1187: }
1188: else if (initc == '#' && STRNCMP(ptr, "el", (size_t)2) == 0)
1189: {
1190: if (count == 0)
1191: return &pos;
1192: }
1193: else if (STRNCMP(ptr, "endif", (size_t)5) == 0)
1194: count++;
1195: }
1196: }
1197: return NULL;
1198: }
1199: }
1200:
1201: #ifdef RIGHTLEFT
1202: findc = (curwin->w_p_rl ? table_rl : table)[idx ^ 1];
1203: #else
1204: findc = table[idx ^ 1]; /* get matching brace */
1205: #endif
1206: idx &= 1;
1207:
1208: do_quotes = -1;
1209: start_in_quotes = MAYBE;
1210: while (!got_int)
1211: {
1212: /*
1213: * Go to the next position, forward or backward. We could use
1214: * inc() and dec() here, but that is much slower
1215: */
1216: if (idx) /* backward search */
1217: {
1218: if (pos.col == 0) /* at start of line, go to prev. one */
1219: {
1220: if (pos.lnum == 1) /* start of file */
1221: break;
1222: --pos.lnum;
1223:
1224: if (maxtravel && traveled++ > maxtravel)
1225: break;
1226:
1227: linep = ml_get(pos.lnum);
1228: pos.col = STRLEN(linep); /* put pos.col on trailing NUL */
1229: do_quotes = -1;
1230: line_breakcheck();
1231: }
1232: else
1233: --pos.col;
1234: }
1235: else /* forward search */
1236: {
1237: if (linep[pos.col] == NUL) /* at end of line, go to next one */
1238: {
1239: if (pos.lnum == curbuf->b_ml.ml_line_count) /* end of file */
1240: break;
1241: ++pos.lnum;
1242:
1243: if (maxtravel && traveled++ > maxtravel)
1244: break;
1245:
1246: linep = ml_get(pos.lnum);
1247: pos.col = 0;
1248: do_quotes = -1;
1249: line_breakcheck();
1250: }
1251: else
1252: ++pos.col;
1253: }
1254:
1255: /*
1256: * If FM_BLOCKSTOP given, stop at a '{' or '}' in column 0.
1257: */
1258: if (pos.col == 0 && (flags & FM_BLOCKSTOP) &&
1259: (linep[0] == '{' || linep[0] == '}'))
1260: {
1261: if (linep[0] == findc && count == 0) /* match! */
1262: return &pos;
1263: break; /* out of scope */
1264: }
1265:
1266: if (comment_dir)
1267: {
1268: /* Note: comments do not nest, and we ignore quotes in them */
1269: if (comment_dir == 1)
1270: {
1271: if (linep[pos.col] == '*' && linep[pos.col + 1] == '/')
1272: {
1273: pos.col++;
1274: return &pos;
1275: }
1276: }
1277: else /* Searching backwards */
1278: {
1279: /*
1280: * A comment may contain slash-star, it may also start or end
1281: * with slash-star-slash. I'm not using real examples though
1282: * because "gcc -Wall" would complain -- webb
1283: */
1284: if (pos.col == 0)
1285: continue;
1286: else if (linep[pos.col - 1] == '/' && linep[pos.col] == '*')
1287: {
1288: count++;
1289: match_pos = pos;
1290: match_pos.col--;
1291: }
1292: else if (linep[pos.col - 1] == '*' && linep[pos.col] == '/')
1293: {
1294: if (count > 0)
1295: pos = match_pos;
1296: else if (pos.col > 1 && linep[pos.col - 2] == '/')
1297: pos.col -= 2;
1298: else if (ignore_cend)
1299: continue;
1300: else
1301: return NULL;
1302: return &pos;
1303: }
1304: }
1305: continue;
1306: }
1307:
1308: /*
1309: * If smart matching ('cpoptions' does not contain '%'), braces inside
1310: * of quotes are ignored, but only if there is an even number of
1311: * quotes in the line.
1312: */
1313: if (cpo_match)
1314: do_quotes = 0;
1315: else if (do_quotes == -1)
1316: {
1317: /*
1318: * count the number of quotes in the line, skipping \" and '"'
1319: */
1320: at_start = do_quotes;
1321: for (ptr = linep; *ptr; ++ptr)
1322: {
1323: if (ptr == linep + curwin->w_cursor.col)
1324: at_start = (do_quotes & 1);
1325: if (*ptr == '"' && (ptr == linep || ptr[-1] != '\\') &&
1326: (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\''))
1327: ++do_quotes;
1328: }
1329: do_quotes &= 1; /* result is 1 with even number of quotes */
1330:
1331: /*
1332: * If we find an uneven count, check current line and previous
1333: * one for a '\' at the end.
1334: */
1335: if (!do_quotes)
1336: {
1337: inquote = FALSE;
1338: if (ptr[-1] == '\\')
1339: {
1340: do_quotes = 1;
1341: if (start_in_quotes == MAYBE)
1342: {
1343: inquote = !at_start;
1344: if (inquote)
1345: start_in_quotes = TRUE;
1346: }
1347: else if (idx) /* backward search */
1348: inquote = TRUE;
1349: }
1350: if (pos.lnum > 1)
1351: {
1352: ptr = ml_get(pos.lnum - 1);
1353: if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\')
1354: {
1355: do_quotes = 1;
1356: if (start_in_quotes == MAYBE)
1357: {
1358: inquote = at_start;
1359: if (inquote)
1360: start_in_quotes = TRUE;
1361: }
1362: else if (!idx) /* forward search */
1363: inquote = TRUE;
1364: }
1365: }
1366: }
1367: }
1368: if (start_in_quotes == MAYBE)
1369: start_in_quotes = FALSE;
1370:
1371: /*
1372: * If 'smartmatch' is set:
1373: * Things inside quotes are ignored by setting 'inquote'. If we
1374: * find a quote without a preceding '\' invert 'inquote'. At the
1375: * end of a line not ending in '\' we reset 'inquote'.
1376: *
1377: * In lines with an uneven number of quotes (without preceding '\')
1378: * we do not know which part to ignore. Therefore we only set
1379: * inquote if the number of quotes in a line is even, unless this
1380: * line or the previous one ends in a '\'. Complicated, isn't it?
1381: */
1382: switch (c = linep[pos.col])
1383: {
1384: case NUL:
1385: /* at end of line without trailing backslash, reset inquote */
1386: if (pos.col == 0 || linep[pos.col - 1] != '\\')
1387: {
1388: inquote = FALSE;
1389: start_in_quotes = FALSE;
1390: }
1391: break;
1392:
1393: case '"':
1394: /* a quote that is preceded with a backslash is ignored */
1395: if (do_quotes && (pos.col == 0 || linep[pos.col - 1] != '\\'))
1396: {
1397: inquote = !inquote;
1398: start_in_quotes = FALSE;
1399: }
1400: break;
1401:
1402: /*
1403: * If smart matching ('cpoptions' does not contain '%'):
1404: * Skip things in single quotes: 'x' or '\x'. Be careful for single
1405: * single quotes, eg jon's. Things like '\233' or '\x3f' are not
1406: * skipped, there is never a brace in them.
1407: */
1408: case '\'':
1409: if (vim_strchr(p_cpo, CPO_MATCH) == NULL)
1410: {
1411: if (idx) /* backward search */
1412: {
1413: if (pos.col > 1)
1414: {
1415: if (linep[pos.col - 2] == '\'')
1416: pos.col -= 2;
1417: else if (linep[pos.col - 2] == '\\' &&
1418: pos.col > 2 && linep[pos.col - 3] == '\'')
1419: pos.col -= 3;
1420: }
1421: }
1422: else if (linep[pos.col + 1]) /* forward search */
1423: {
1424: if (linep[pos.col + 1] == '\\' &&
1425: linep[pos.col + 2] && linep[pos.col + 3] == '\'')
1426: pos.col += 3;
1427: else if (linep[pos.col + 2] == '\'')
1428: pos.col += 2;
1429: }
1430: }
1431: break;
1432:
1433: default:
1434: /* Check for match outside of quotes, and inside of
1435: * quotes when the start is also inside of quotes */
1436: if (!inquote || start_in_quotes == TRUE)
1437: {
1438: if (c == initc)
1439: count++;
1440: else if (c == findc)
1441: {
1442: if (count == 0)
1443: return &pos;
1444: count--;
1445: }
1446: }
1447: }
1448: }
1449:
1450: if (comment_dir == -1 && count > 0)
1451: {
1452: pos = match_pos;
1453: return &pos;
1454: }
1455: return (FPOS *) NULL; /* never found it */
1456: }
1457:
1458: /*
1459: * Move cursor briefly to character matching the one under the cursor.
1460: * Show the match only if it is visible on the screen.
1461: */
1462: void
1463: showmatch()
1464: {
1465: FPOS *lpos, csave;
1466: colnr_t vcol;
1467:
1468: if ((lpos = findmatch(NUL)) == NULL) /* no match, so beep */
1469: beep_flush();
1470: else if (lpos->lnum >= curwin->w_topline)
1471: {
1472: if (!curwin->w_p_wrap)
1473: getvcol(curwin, lpos, NULL, &vcol, NULL);
1474: if (curwin->w_p_wrap || (vcol >= curwin->w_leftcol &&
1475: vcol < curwin->w_leftcol + Columns))
1476: {
1477: updateScreen(VALID_TO_CURSCHAR); /* show the new char first */
1478: csave = curwin->w_cursor;
1479: curwin->w_cursor = *lpos; /* move to matching char */
1480: cursupdate();
1481: showruler(0);
1482: setcursor();
1483: cursor_on(); /* make sure that the cursor is shown */
1484: flushbuf();
1485:
1486: /*
1487: * brief pause, unless 'm' is present in 'cpo' and a character is
1488: * available.
1489: */
1490: if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL)
1491: mch_delay(500L, TRUE);
1492: else if (!char_avail())
1493: mch_delay(500L, FALSE);
1494: curwin->w_cursor = csave; /* restore cursor position */
1495: cursupdate();
1496: }
1497: }
1498: }
1499:
1500: /*
1501: * findsent(dir, count) - Find the start of the next sentence in direction
1502: * 'dir' Sentences are supposed to end in ".", "!" or "?" followed by white
1503: * space or a line break. Also stop at an empty line.
1504: * Return OK if the next sentence was found.
1505: */
1506: int
1507: findsent(dir, count)
1508: int dir;
1509: long count;
1510: {
1511: FPOS pos, tpos;
1512: register int c;
1513: int (*func) __PARMS((FPOS *));
1514: int startlnum;
1515: int noskip = FALSE; /* do not skip blanks */
1516:
1517: pos = curwin->w_cursor;
1518: if (dir == FORWARD)
1519: func = incl;
1520: else
1521: func = decl;
1522:
1523: while (count--)
1524: {
1525: /*
1526: * if on an empty line, skip upto a non-empty line
1527: */
1528: if (gchar(&pos) == NUL)
1529: {
1530: do
1531: if ((*func)(&pos) == -1)
1532: break;
1533: while (gchar(&pos) == NUL);
1534: if (dir == FORWARD)
1535: goto found;
1536: }
1537: /*
1538: * if on the start of a paragraph or a section and searching forward,
1539: * go to the next line
1540: */
1541: else if (dir == FORWARD && pos.col == 0 &&
1542: startPS(pos.lnum, NUL, FALSE))
1543: {
1544: if (pos.lnum == curbuf->b_ml.ml_line_count)
1545: return FAIL;
1546: ++pos.lnum;
1547: goto found;
1548: }
1549: else if (dir == BACKWARD)
1550: decl(&pos);
1551:
1552: /* go back to the previous non-blank char */
1553: while ((c = gchar(&pos)) == ' ' || c == '\t' ||
1554: (dir == BACKWARD && vim_strchr((char_u *)".!?)]\"'", c) != NULL))
1555: {
1556: if (decl(&pos) == -1)
1557: break;
1558: /* when going forward: Stop in front of empty line */
1559: if (lineempty(pos.lnum) && dir == FORWARD)
1560: {
1561: incl(&pos);
1562: goto found;
1563: }
1564: }
1565:
1566: /* remember the line where the search started */
1567: startlnum = pos.lnum;
1568:
1569: for (;;) /* find end of sentence */
1570: {
1571: c = gchar(&pos);
1572: if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE)))
1573: {
1574: if (dir == BACKWARD && pos.lnum != startlnum)
1575: ++pos.lnum;
1576: break;
1577: }
1578: if (c == '.' || c == '!' || c == '?')
1579: {
1580: tpos = pos;
1581: do
1582: if ((c = inc(&tpos)) == -1)
1583: break;
1584: while (vim_strchr((char_u *)")]\"'", c = gchar(&tpos)) != NULL);
1585: if (c == -1 || c == ' ' || c == '\t' || c == NUL)
1586: {
1587: pos = tpos;
1588: if (gchar(&pos) == NUL) /* skip NUL at EOL */
1589: inc(&pos);
1590: break;
1591: }
1592: }
1593: if ((*func)(&pos) == -1)
1594: {
1595: if (count)
1596: return FAIL;
1597: noskip = TRUE;
1598: break;
1599: }
1600: }
1601: found:
1602: /* skip white space */
1603: while (!noskip && ((c = gchar(&pos)) == ' ' || c == '\t'))
1604: if (incl(&pos) == -1)
1605: break;
1606: }
1607:
1608: setpcmark();
1609: curwin->w_cursor = pos;
1610: return OK;
1611: }
1612:
1613: /*
1614: * findpar(dir, count, what) - Find the next paragraph in direction 'dir'
1615: * Paragraphs are currently supposed to be separated by empty lines.
1616: * Return TRUE if the next paragraph was found.
1617: * If 'what' is '{' or '}' we go to the next section.
1618: * If 'both' is TRUE also stop at '}'.
1619: */
1620: int
1621: findpar(dir, count, what, both)
1622: register int dir;
1623: long count;
1624: int what;
1625: int both;
1626: {
1627: register linenr_t curr;
1628: int did_skip; /* TRUE after separating lines have
1629: been skipped */
1630: int first; /* TRUE on first line */
1631:
1632: curr = curwin->w_cursor.lnum;
1633:
1634: while (count--)
1635: {
1636: did_skip = FALSE;
1637: for (first = TRUE; ; first = FALSE)
1638: {
1639: if (*ml_get(curr) != NUL)
1640: did_skip = TRUE;
1641:
1642: if (!first && did_skip && startPS(curr, what, both))
1643: break;
1644:
1645: if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count)
1646: {
1647: if (count)
1648: return FALSE;
1649: curr -= dir;
1650: break;
1651: }
1652: }
1653: }
1654: setpcmark();
1655: if (both && *ml_get(curr) == '}') /* include line with '}' */
1656: ++curr;
1657: curwin->w_cursor.lnum = curr;
1658: if (curr == curbuf->b_ml.ml_line_count)
1659: {
1660: if ((curwin->w_cursor.col = STRLEN(ml_get(curr))) != 0)
1661: {
1662: --curwin->w_cursor.col;
1663: op_inclusive = TRUE;
1664: }
1665: }
1666: else
1667: curwin->w_cursor.col = 0;
1668: return TRUE;
1669: }
1670:
1671: /*
1672: * check if the string 's' is a nroff macro that is in option 'opt'
1673: */
1674: static int
1675: inmacro(opt, s)
1676: char_u *opt;
1677: register char_u *s;
1678: {
1679: register char_u *macro;
1680:
1681: for (macro = opt; macro[0]; ++macro)
1682: {
1683: if (macro[0] == s[0] && (((s[1] == NUL || s[1] == ' ') &&
1684: (macro[1] == NUL || macro[1] == ' ')) || macro[1] == s[1]))
1685: break;
1686: ++macro;
1687: if (macro[0] == NUL)
1688: break;
1689: }
1690: return (macro[0] != NUL);
1691: }
1692:
1693: /*
1694: * startPS: return TRUE if line 'lnum' is the start of a section or paragraph.
1695: * If 'para' is '{' or '}' only check for sections.
1696: * If 'both' is TRUE also stop at '}'
1697: */
1698: int
1699: startPS(lnum, para, both)
1700: linenr_t lnum;
1701: int para;
1702: int both;
1703: {
1704: register char_u *s;
1705:
1706: s = ml_get(lnum);
1707: if (*s == para || *s == '\f' || (both && *s == '}'))
1708: return TRUE;
1709: if (*s == '.' && (inmacro(p_sections, s + 1) ||
1710: (!para && inmacro(p_para, s + 1))))
1711: return TRUE;
1712: return FALSE;
1713: }
1714:
1715: /*
1716: * The following routines do the word searches performed by the 'w', 'W',
1717: * 'b', 'B', 'e', and 'E' commands.
1718: */
1719:
1720: /*
1721: * To perform these searches, characters are placed into one of three
1722: * classes, and transitions between classes determine word boundaries.
1723: *
1724: * The classes are:
1725: *
1726: * 0 - white space
1727: * 1 - keyword charactes (letters, digits and underscore)
1728: * 2 - everything else
1729: */
1730:
1731: static int stype; /* type of the word motion being performed */
1732:
1733: /*
1734: * cls() - returns the class of character at curwin->w_cursor
1735: *
1736: * The 'type' of the current search modifies the classes of characters if a
1737: * 'W', 'B', or 'E' motion is being done. In this case, chars. from class 2
1738: * are reported as class 1 since only white space boundaries are of interest.
1739: */
1740: static int
1741: cls()
1742: {
1743: register int c;
1744:
1745: c = gchar_cursor();
1746: if (c == ' ' || c == '\t' || c == NUL)
1747: return 0;
1748:
1749: if (iswordchar(c))
1750: return 1;
1751:
1752: /*
1753: * If stype is non-zero, report these as class 1.
1754: */
1755: return (stype == 0) ? 2 : 1;
1756: }
1757:
1758:
1759: /*
1760: * fwd_word(count, type, eol) - move forward one word
1761: *
1762: * Returns FAIL if the cursor was already at the end of the file.
1763: * If eol is TRUE, last word stops at end of line (for operators).
1764: */
1765: int
1766: fwd_word(count, type, eol)
1767: long count;
1768: int type;
1769: int eol;
1770: {
1771: int sclass; /* starting class */
1772: int i;
1773: int last_line;
1774:
1775: stype = type;
1776: while (--count >= 0)
1777: {
1778: sclass = cls();
1779:
1780: /*
1781: * We always move at least one character, unless on the last character
1782: * in the buffer.
1783: */
1784: last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count);
1785: i = inc_cursor();
1786: if (i == -1 || (i == 1 && last_line))
1787: /* started at last char in file */
1788: return FAIL;
1789: if (i == 1 && eol && count == 0) /* started at last char in line */
1790: return OK;
1791:
1792: /*
1793: * Go one char past end of current word (if any)
1794: */
1795: if (sclass != 0)
1796: while (cls() == sclass)
1797: {
1798: i = inc_cursor();
1799: if (i == -1 || (i == 1 && eol && count == 0))
1800: return OK;
1801: }
1802:
1803: /*
1804: * go to next non-white
1805: */
1806: while (cls() == 0)
1807: {
1808: /*
1809: * We'll stop if we land on a blank line
1810: */
1811: if (curwin->w_cursor.col == 0 && *ml_get_curline() == NUL)
1812: break;
1813:
1814: i = inc_cursor();
1815: if (i == -1 || (i == 1 && eol && count == 0))
1816: return OK;
1817: }
1818: }
1819: return OK;
1820: }
1821:
1822: /*
1823: * bck_word() - move backward 'count' words
1824: *
1825: * If stop is TRUE and we are already on the start of a word, move one less.
1826: *
1827: * Returns FAIL if top of the file was reached.
1828: */
1829: int
1830: bck_word(count, type, stop)
1831: long count;
1832: int type;
1833: int stop;
1834: {
1835: int sclass; /* starting class */
1836:
1837: stype = type;
1838: while (--count >= 0)
1839: {
1840: sclass = cls();
1841: if (dec_cursor() == -1) /* started at start of file */
1842: return FAIL;
1843:
1844: if (!stop || sclass == cls() || sclass == 0)
1845: {
1846: /*
1847: * Skip white space before the word.
1848: * Stop on an empty line.
1849: */
1850: while (cls() == 0)
1851: {
1852: if (curwin->w_cursor.col == 0 &&
1853: lineempty(curwin->w_cursor.lnum))
1854: goto finished;
1855:
1856: if (dec_cursor() == -1) /* hit start of file, stop here */
1857: return OK;
1858: }
1859:
1860: /*
1861: * Move backward to start of this word.
1862: */
1863: if (skip_chars(cls(), BACKWARD))
1864: return OK;
1865: }
1866:
1867: inc_cursor(); /* overshot - forward one */
1868: finished:
1869: stop = FALSE;
1870: }
1871: return OK;
1872: }
1873:
1874: /*
1875: * end_word() - move to the end of the word
1876: *
1877: * There is an apparent bug in the 'e' motion of the real vi. At least on the
1878: * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e'
1879: * motion crosses blank lines. When the real vi crosses a blank line in an
1880: * 'e' motion, the cursor is placed on the FIRST character of the next
1881: * non-blank line. The 'E' command, however, works correctly. Since this
1882: * appears to be a bug, I have not duplicated it here.
1883: *
1884: * Returns FAIL if end of the file was reached.
1885: *
1886: * If stop is TRUE and we are already on the end of a word, move one less.
1887: * If empty is TRUE stop on an empty line.
1888: */
1889: int
1890: end_word(count, type, stop, empty)
1891: long count;
1892: int type;
1893: int stop;
1894: int empty;
1895: {
1896: int sclass; /* starting class */
1897:
1898: stype = type;
1899: while (--count >= 0)
1900: {
1901: sclass = cls();
1902: if (inc_cursor() == -1)
1903: return FAIL;
1904:
1905: /*
1906: * If we're in the middle of a word, we just have to move to the end
1907: * of it.
1908: */
1909: if (cls() == sclass && sclass != 0)
1910: {
1911: /*
1912: * Move forward to end of the current word
1913: */
1914: if (skip_chars(sclass, FORWARD))
1915: return FAIL;
1916: }
1917: else if (!stop || sclass == 0)
1918: {
1919: /*
1920: * We were at the end of a word. Go to the end of the next word.
1921: * First skip white space, if 'empty' is TRUE, stop at empty line.
1922: */
1923: while (cls() == 0)
1924: {
1925: if (empty && curwin->w_cursor.col == 0 &&
1926: lineempty(curwin->w_cursor.lnum))
1927: goto finished;
1928: if (inc_cursor() == -1) /* hit end of file, stop here */
1929: return FAIL;
1930: }
1931:
1932: /*
1933: * Move forward to the end of this word.
1934: */
1935: if (skip_chars(cls(), FORWARD))
1936: return FAIL;
1937: }
1938: dec_cursor(); /* overshot - one char backward */
1939: finished:
1940: stop = FALSE; /* we move only one word less */
1941: }
1942: return OK;
1943: }
1944:
1945: /*
1946: * bckend_word(count, type) - move back to the end of the word
1947: *
1948: * If 'eol' is TRUE, stop at end of line.
1949: *
1950: * Returns FAIL if start of the file was reached.
1951: */
1952: int
1953: bckend_word(count, type, eol)
1954: long count;
1955: int type;
1956: int eol;
1957: {
1958: int sclass; /* starting class */
1959: int i;
1960:
1961: stype = type;
1962: while (--count >= 0)
1963: {
1964: sclass = cls();
1965: if ((i = dec_cursor()) == -1)
1966: return FAIL;
1967: if (eol && i == 1)
1968: return OK;
1969:
1970: /*
1971: * Move backward to before the start of this word.
1972: */
1973: if (sclass != 0)
1974: {
1975: while (cls() == sclass)
1976: if ((i = dec_cursor()) == -1 || (eol && i == 1))
1977: return OK;
1978: }
1979:
1980: /*
1981: * Move backward to end of the previous word
1982: */
1983: while (cls() == 0)
1984: {
1985: if (curwin->w_cursor.col == 0 && lineempty(curwin->w_cursor.lnum))
1986: break;
1987: if ((i = dec_cursor()) == -1 || (eol && i == 1))
1988: return OK;
1989: }
1990: }
1991: return OK;
1992: }
1993:
1994: static int
1995: skip_chars(cclass, dir)
1996: int cclass;
1997: int dir;
1998: {
1999: while (cls() == cclass)
2000: if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1)
2001: return TRUE;
2002: return FALSE;
2003: }
2004:
2005: /*
2006: * Go back to the start of the word or the start of white space
2007: */
2008: static void
2009: back_in_line()
2010: {
2011: int sclass; /* starting class */
2012:
2013: sclass = cls();
2014: for (;;)
2015: {
2016: if (curwin->w_cursor.col == 0) /* stop at start of line */
2017: break;
2018: --curwin->w_cursor.col;
2019: if (cls() != sclass) /* stop at start of word */
2020: {
2021: ++curwin->w_cursor.col;
2022: break;
2023: }
2024: }
2025: }
2026:
2027: /*
2028: * Find word under cursor, cursor at end
2029: */
2030: int
2031: current_word(count, type)
2032: long count;
2033: int type;
2034: {
2035: FPOS start;
2036: FPOS pos;
2037: int inclusive = TRUE;
2038:
2039: stype = type;
2040:
2041: /*
2042: * When visual area is bigger than one character: Extend it.
2043: */
2044: if (VIsual_active && !equal(curwin->w_cursor, VIsual))
2045: {
2046: if (lt(curwin->w_cursor, VIsual))
2047: {
2048: if (decl(&curwin->w_cursor) == -1)
2049: return FAIL;
2050: if (cls() == 0)
2051: {
2052: if (bckend_word(count, type, TRUE) == FAIL)
2053: return FAIL;
2054: (void)incl(&curwin->w_cursor);
2055: }
2056: else
2057: {
2058: if (bck_word(count, type, TRUE) == FAIL)
2059: return FAIL;
2060: }
2061: }
2062: else
2063: {
2064: if (incl(&curwin->w_cursor) == -1)
2065: return FAIL;
2066: if (cls() == 0)
2067: {
2068: if (fwd_word(count, type, TRUE) == FAIL)
2069: return FAIL;
2070: (void)oneleft();
2071: }
2072: else
2073: {
2074: if (end_word(count, type, TRUE, TRUE) == FAIL)
2075: return FAIL;
2076: }
2077: }
2078: return OK;
2079: }
2080:
2081: /*
2082: * Go to start of current word or white space.
2083: */
2084: back_in_line();
2085: start = curwin->w_cursor;
2086:
2087: /*
2088: * If the start is on white space, find end of word.
2089: * Otherwise find start of next word.
2090: */
2091: if (cls() == 0)
2092: {
2093: if (end_word(count, type, TRUE, TRUE) == FAIL)
2094: return FAIL;
2095: }
2096: else
2097: {
2098: if (fwd_word(count, type, TRUE) == FAIL)
2099: return FAIL;
2100: /*
2101: * If end is just past a new-line, we don't want to include the first
2102: * character on the line
2103: */
2104: if (oneleft() == FAIL) /* put cursor on last char of area */
2105: inclusive = FALSE;
2106: else
2107: {
2108: pos = curwin->w_cursor; /* save cursor position */
2109: /*
2110: * If we don't include white space at the end, move the start to
2111: * include some white space there. This makes "d." work better on
2112: * the last word in a sentence. Don't delete white space at start
2113: * of line (indent).
2114: */
2115: if (cls() != 0)
2116: {
2117: curwin->w_cursor = start;
2118: if (oneleft() == OK)
2119: {
2120: back_in_line();
2121: if (cls() == 0 && curwin->w_cursor.col > 0)
2122: start = curwin->w_cursor;
2123: }
2124: }
2125: curwin->w_cursor = pos; /* put cursor back at end */
2126: }
2127: }
2128: if (VIsual_active)
2129: {
2130: /* should do something when inclusive == FALSE ! */
2131: VIsual = start;
2132: VIsual_mode = 'v';
2133: update_curbuf(NOT_VALID); /* update the inversion */
2134: }
2135: else
2136: {
2137: curbuf->b_op_start = start;
2138: op_motion_type = MCHAR;
2139: op_inclusive = inclusive;
2140: }
2141: return OK;
2142: }
2143:
2144: /*
2145: * Find sentence under the cursor, cursor at end.
2146: */
2147: int
2148: current_sent(count)
2149: long count;
2150: {
2151: FPOS start;
2152: FPOS pos;
2153: int start_blank;
2154: int c;
2155:
2156: pos = curwin->w_cursor;
2157: start = pos;
2158:
2159: /*
2160: * When visual area is bigger than one character: Extend it.
2161: */
2162: if (VIsual_active && !equal(curwin->w_cursor, VIsual))
2163: {
2164: if (lt(pos, VIsual))
2165: {
2166: /*
2167: * Do a "sentence backward" on the next character.
2168: * If we end up on the same position, we were already at the start
2169: * of a sentence
2170: */
2171: if (incl(&curwin->w_cursor) == -1)
2172: return FAIL;
2173: findsent(BACKWARD, 1L);
2174: start = curwin->w_cursor;
2175: if (count > 1)
2176: findsent(BACKWARD, count - 1);
2177: /*
2178: * When at start of sentence: Include blanks in front of sentence.
2179: * Use current_word() to cross line boundaries.
2180: * If we don't end up on a blank or on an empty line, go back to
2181: * the start of the previous sentence.
2182: */
2183: if (equal(pos, start))
2184: {
2185: current_word(1L, 0);
2186: c = gchar_cursor();
2187: if (c != NUL && !vim_iswhite(c))
2188: findsent(BACKWARD, 1L);
2189: }
2190:
2191: }
2192: else
2193: {
2194: /*
2195: * When one char before start of sentence: Don't include blanks in
2196: * front of next sentence.
2197: * Else go count sentences forward.
2198: */
2199: findsent(FORWARD, 1L);
2200: incl(&pos);
2201: if (equal(pos, curwin->w_cursor))
2202: {
2203: findsent(FORWARD, count);
2204: find_first_blank(&curwin->w_cursor);
2205: }
2206: else if (count > 1)
2207: findsent(FORWARD, count - 1);
2208: decl(&curwin->w_cursor);
2209: }
2210: return OK;
2211: }
2212:
2213: /*
2214: * Find start of next sentence.
2215: */
2216: findsent(FORWARD, 1L);
2217:
2218: /*
2219: * If cursor started on blank, check if it is just before the start of the
2220: * next sentence.
2221: */
2222: while (vim_iswhite(gchar(&pos)))
2223: incl(&pos);
2224: if (equal(pos, curwin->w_cursor))
2225: {
2226: start_blank = TRUE;
2227: /*
2228: * go back to first blank
2229: */
2230: while (decl(&start) != -1)
2231: {
2232: if (!vim_iswhite(gchar(&start)))
2233: {
2234: incl(&start);
2235: break;
2236: }
2237: }
2238: }
2239: else
2240: {
2241: start_blank = FALSE;
2242: findsent(BACKWARD, 1L);
2243: start = curwin->w_cursor;
2244: }
2245: findsent(FORWARD, count);
2246:
2247: /*
2248: * If the blank in front of the sentence is included, exclude the blanks
2249: * at the end of the sentence, go back to the first blank.
2250: */
2251: if (start_blank)
2252: find_first_blank(&curwin->w_cursor);
2253: else
2254: {
2255: /*
2256: * If there are no trailing blanks, try to include leading blanks
2257: */
2258: pos = curwin->w_cursor;
2259: decl(&pos);
2260: if (!vim_iswhite(gchar(&pos)))
2261: find_first_blank(&start);
2262: }
2263:
2264: if (VIsual_active)
2265: {
2266: VIsual = start;
2267: VIsual_mode = 'v';
2268: decl(&curwin->w_cursor); /* don't include the cursor char */
2269: update_curbuf(NOT_VALID); /* update the inversion */
2270: }
2271: else
2272: {
2273: curbuf->b_op_start = start;
2274: op_motion_type = MCHAR;
2275: op_inclusive = FALSE;
2276: }
2277: return OK;
2278: }
2279:
2280: int
2281: current_block(what, count)
2282: int what; /* '(' or '{' */
2283: long count;
2284: {
2285: FPOS old_pos;
2286: FPOS *pos = NULL;
2287: FPOS start_pos;
2288: FPOS *end_pos;
2289: FPOS old_start, old_end;
2290: int inclusive = FALSE;
2291: int other;
2292:
2293: old_pos = curwin->w_cursor;
2294: if (what == '{')
2295: other = '}';
2296: else
2297: other = ')';
2298:
2299: old_end = curwin->w_cursor; /* remember where we started */
2300: old_start = old_end;
2301:
2302: /*
2303: * If we start on '(', '{', ')' or '}', use the whole block inclusive.
2304: */
2305: if (!VIsual_active || (VIsual.lnum == curwin->w_cursor.lnum &&
2306: VIsual.col == curwin->w_cursor.col))
2307: {
2308: if (what == '{') /* ignore indent */
2309: while (inindent(1))
2310: if (inc_cursor() != 0)
2311: break;
2312: if (gchar_cursor() == what) /* cursor on '(' or '{' */
2313: {
2314: ++curwin->w_cursor.col;
2315: inclusive = TRUE;
2316: }
2317: else if (gchar_cursor() == other) /* cursor on ')' or '}' */
2318: inclusive = TRUE;
2319: }
2320: else if (lt(VIsual, curwin->w_cursor))
2321: {
2322: old_start = VIsual;
2323: curwin->w_cursor = VIsual; /* cursor at low end of Visual */
2324: }
2325: else
2326: old_end = VIsual;
2327:
2328: /*
2329: * Search backwards for unclosed '(' or '{'.
2330: * put this position in start_pos.
2331: */
2332: while (count--)
2333: {
2334: if ((pos = findmatch(what)) == NULL)
2335: break;
2336: curwin->w_cursor = *pos;
2337: start_pos = *pos; /* the findmatch for end_pos will overwrite *pos */
2338: }
2339:
2340: /*
2341: * Search for matching ')' or '}'.
2342: * Put this position in curwin->w_cursor.
2343: */
2344: if (pos == NULL || (end_pos = findmatch(other)) == NULL)
2345: {
2346: curwin->w_cursor = old_pos;
2347: return FAIL;
2348: }
2349: curwin->w_cursor = *end_pos;
2350:
2351: /*
2352: * Try to exclude the '(', '{', ')' and '}'.
2353: * If the ending '}' is only preceded by indent, skip that indent.
2354: * But only if the resulting area is not smaller than what we started with.
2355: */
2356: if (!inclusive)
2357: {
2358: incl(&start_pos);
2359: old_pos = curwin->w_cursor;
2360: decl(&curwin->w_cursor);
2361: if (what == '{')
2362: while (inindent(0))
2363: if (decl(&curwin->w_cursor) != 0)
2364: break;
2365: if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor))
2366: {
2367: decl(&start_pos);
2368: curwin->w_cursor = old_pos;
2369: }
2370: }
2371:
2372: if (VIsual_active)
2373: {
2374: VIsual = start_pos;
2375: VIsual_mode = 'v';
2376: update_curbuf(NOT_VALID); /* update the inversion */
2377: showmode();
2378: }
2379: else
2380: {
2381: curbuf->b_op_start = start_pos;
2382: op_motion_type = MCHAR;
2383: op_inclusive = TRUE;
2384: }
2385:
2386: return OK;
2387: }
2388:
2389: int
2390: current_par(type, count)
2391: int type; /* 'p' for paragraph, 'S' for section */
2392: long count;
2393: {
2394: linenr_t start;
2395: linenr_t end;
2396: int white_in_front;
2397: int dir;
2398: int start_is_white;
2399: int retval = OK;
2400:
2401: if (type == 'S') /* not implemented yet */
2402: return FAIL;
2403:
2404: start = curwin->w_cursor.lnum;
2405:
2406: /*
2407: * When visual area is more than one line: extend it.
2408: */
2409: if (VIsual_active && start != VIsual.lnum)
2410: {
2411: if (start < VIsual.lnum)
2412: dir = BACKWARD;
2413: else
2414: dir = FORWARD;
2415: while (count--)
2416: {
2417: if (start == (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count))
2418: retval = FAIL;
2419:
2420: start_is_white = -1;
2421: for (;;)
2422: {
2423: if (start == (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count))
2424: break;
2425: if (start_is_white >= 0 &&
2426: (start_is_white != linewhite(start + dir) ||
2427: startPS(start + (dir > 0 ? 1 : 0), 0, 0)))
2428: break;
2429: start += dir;
2430: if (start_is_white < 0)
2431: start_is_white = linewhite(start);
2432: }
2433: }
2434: curwin->w_cursor.lnum = start;
2435: curwin->w_cursor.col = 0;
2436: return retval;
2437: }
2438:
2439: /*
2440: * First move back to the start of the paragraph or white lines
2441: */
2442: white_in_front = linewhite(start);
2443: while (start > 1)
2444: {
2445: if (white_in_front) /* stop at first white line */
2446: {
2447: if (!linewhite(start - 1))
2448: break;
2449: }
2450: else /* stop at first non-white line of start of paragraph */
2451: {
2452: if (linewhite(start - 1) || startPS(start, 0, 0))
2453: break;
2454: }
2455: --start;
2456: }
2457:
2458: /*
2459: * Move past the end of the white lines.
2460: */
2461: end = start;
2462: while (linewhite(end) && end < curbuf->b_ml.ml_line_count)
2463: ++end;
2464:
2465: --end;
2466: while (count--)
2467: {
2468: if (end == curbuf->b_ml.ml_line_count)
2469: return FAIL;
2470:
2471: ++end;
2472: /*
2473: * skip to end of paragraph
2474: */
2475: while (end < curbuf->b_ml.ml_line_count &&
2476: !linewhite(end + 1) && !startPS(end + 1, 0, 0))
2477: ++end;
2478:
2479: if (count == 0 && white_in_front)
2480: break;
2481:
2482: /*
2483: * skip to end of white lines after paragraph
2484: */
2485: while (end < curbuf->b_ml.ml_line_count && linewhite(end + 1))
2486: ++end;
2487: }
2488:
2489: /*
2490: * If there are no empty lines at the end, try to find some empty lines at
2491: * the start (unless that has been done already).
2492: */
2493: if (!white_in_front && !linewhite(end))
2494: while (start > 1 && linewhite(start - 1))
2495: --start;
2496:
2497: if (VIsual_active)
2498: {
2499: VIsual.lnum = start;
2500: VIsual_mode = 'V';
2501: update_curbuf(NOT_VALID); /* update the inversion */
2502: showmode();
2503: }
2504: else
2505: {
2506: curbuf->b_op_start.lnum = start;
2507: op_motion_type = MLINE;
2508: }
2509: curwin->w_cursor.lnum = end;
2510: curwin->w_cursor.col = 0;
2511:
2512: return OK;
2513: }
2514:
2515: /*
2516: * linewhite -- return TRUE if line 'lnum' is empty or has white chars only.
2517: */
2518: int
2519: linewhite(lnum)
2520: linenr_t lnum;
2521: {
2522: char_u *p;
2523:
2524: p = skipwhite(ml_get(lnum));
2525: return (*p == NUL);
2526: }
2527:
2528: static void
2529: find_first_blank(posp)
2530: FPOS *posp;
2531: {
2532: while (decl(posp) != -1)
2533: {
2534: if (!vim_iswhite(gchar(posp)))
2535: {
2536: incl(posp);
2537: break;
2538: }
2539: }
2540: }
2541:
2542: void
2543: find_pattern_in_path(ptr, len, whole, skip_comments,
2544: type, count, action, start_lnum, end_lnum)
2545: char_u *ptr; /* pointer to search pattern */
2546: int len; /* length of search pattern */
2547: int whole; /* match whole words only */
2548: int skip_comments; /* don't match inside comments */
2549: int type; /* Type of search; are we looking for a type? a
2550: macro? */
2551: long count;
2552: int action; /* What to do when we find it */
2553: linenr_t start_lnum; /* first line to start searching */
2554: linenr_t end_lnum; /* last line for searching */
2555: {
2556: SearchedFile *files; /* Stack of included files */
2557: SearchedFile *bigger; /* When we need more space */
2558: int max_path_depth = 50;
2559: long match_count = 1;
2560:
2561: char_u *pat;
2562: char_u *new_fname;
2563: char_u *curr_fname = curbuf->b_xfilename;
2564: char_u *prev_fname = NULL;
2565: linenr_t lnum;
2566: int depth;
2567: int depth_displayed; /* For type==CHECK_PATH */
2568: int old_files;
2569: int already_searched;
2570: char_u *file_line;
2571: char_u *line;
2572: char_u *p;
2573: char_u *p2 = NUL; /* Init for gcc */
2574: char_u save_char = NUL;
2575: int define_matched;
2576: struct regexp *prog = NULL;
2577: struct regexp *include_prog = NULL;
2578: struct regexp *define_prog = NULL;
2579: int matched = FALSE;
2580: int did_show = FALSE;
2581: int found = FALSE;
2582: int i;
2583:
2584: file_line = alloc(LSIZE);
2585: if (file_line == NULL)
2586: return;
2587:
2588: reg_magic = p_magic;
2589: if (type != CHECK_PATH)
2590: {
2591: pat = alloc(len + 5);
2592: if (pat == NULL)
2593: goto fpip_end;
2594: sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", len, ptr);
2595: set_reg_ic(pat); /* set reg_ic according to p_ic, p_scs and pat */
2596: prog = vim_regcomp(pat);
2597: vim_free(pat);
2598: if (prog == NULL)
2599: goto fpip_end;
2600: }
2601: reg_ic = FALSE; /* don't ignore case in include and define patterns */
2602: if (*p_inc != NUL)
2603: {
2604: include_prog = vim_regcomp(p_inc);
2605: if (include_prog == NULL)
2606: goto fpip_end;
2607: }
2608: if (type == FIND_DEFINE && *p_def != NUL)
2609: {
2610: define_prog = vim_regcomp(p_def);
2611: if (define_prog == NULL)
2612: goto fpip_end;
2613: }
2614: files = (SearchedFile *)lalloc(max_path_depth * sizeof(SearchedFile), TRUE);
2615: if (files == NULL)
2616: goto fpip_end;
2617: for (i = 0; i < max_path_depth; i++)
2618: {
2619: files[i].fp = NULL;
2620: files[i].name = NULL;
2621: files[i].lnum = 0;
2622: files[i].matched = FALSE;
2623: }
2624: old_files = max_path_depth;
2625: depth = depth_displayed = -1;
2626:
2627: lnum = start_lnum;
2628: if (end_lnum > curbuf->b_ml.ml_line_count)
2629: end_lnum = curbuf->b_ml.ml_line_count;
2630: if (lnum > end_lnum) /* do at least one line */
2631: lnum = end_lnum;
2632: line = ml_get(lnum);
2633:
2634: for (;;)
2635: {
2636: if (include_prog != NULL && vim_regexec(include_prog, line, TRUE))
2637: {
2638: new_fname = get_file_name_in_path(include_prog->endp[0] + 1,
2639: 0, FNAME_EXP);
2640: already_searched = FALSE;
2641: if (new_fname != NULL)
2642: {
2643: /* Check whether we have already searched in this file */
2644: for (i = 0;; i++)
2645: {
2646: if (i == depth + 1)
2647: i = old_files;
2648: if (i == max_path_depth)
2649: break;
2650: if (STRCMP(new_fname, files[i].name) == 0)
2651: {
2652: if (type != CHECK_PATH &&
2653: action == ACTION_SHOW_ALL && files[i].matched)
2654: {
2655: msg_outchar('\n'); /* cursor below last one */
2656: if (!got_int) /* don't display if 'q'
2657: typed at "--more--"
2658: mesage */
2659: {
2660: set_highlight('d'); /* Same as for dirs */
2661: start_highlight();
2662: msg_home_replace(new_fname);
2663: stop_highlight();
2664: MSG_OUTSTR(" (includes previously listed match)");
2665: prev_fname = NULL;
2666: }
2667: }
2668: vim_free(new_fname);
2669: new_fname = NULL;
2670: already_searched = TRUE;
2671: break;
2672: }
2673: }
2674: }
2675:
2676: if (type == CHECK_PATH && (action == ACTION_SHOW_ALL ||
2677: (new_fname == NULL && !already_searched)))
2678: {
2679: if (did_show)
2680: msg_outchar('\n'); /* cursor below last one */
2681: else
2682: {
2683: gotocmdline(TRUE); /* cursor at status line */
2684: set_highlight('t'); /* Highlight title */
2685: start_highlight();
2686: MSG_OUTSTR("--- Included files ");
2687: if (action != ACTION_SHOW_ALL)
2688: MSG_OUTSTR("not found ");
2689: MSG_OUTSTR("in path ---\n");
2690: stop_highlight();
2691: }
2692: did_show = TRUE;
2693: while (depth_displayed < depth && !got_int)
2694: {
2695: ++depth_displayed;
2696: for (i = 0; i < depth_displayed; i++)
2697: MSG_OUTSTR(" ");
2698: msg_home_replace(files[depth_displayed].name);
2699: MSG_OUTSTR(" -->\n");
2700: }
2701: if (!got_int) /* don't display if 'q' typed
2702: for "--more--" message */
2703: {
2704: for (i = 0; i <= depth_displayed; i++)
2705: MSG_OUTSTR(" ");
2706: set_highlight('d'); /* Same as for directories */
2707: start_highlight();
2708: /*
2709: * Isolate the file name.
2710: * Include the surrounding "" or <> if present.
2711: */
2712: for (p = include_prog->endp[0] + 1; !isfilechar(*p); p++)
2713: ;
2714: for (i = 0; isfilechar(p[i]); i++)
2715: ;
2716: if (p[-1] == '"' || p[-1] == '<')
2717: {
2718: --p;
2719: ++i;
2720: }
2721: if (p[i] == '"' || p[i] == '>')
2722: ++i;
2723: save_char = p[i];
2724: p[i] = NUL;
2725: msg_outstr(p);
2726: p[i] = save_char;
2727: stop_highlight();
2728: if (new_fname == NULL && action == ACTION_SHOW_ALL)
2729: {
2730: if (already_searched)
2731: MSG_OUTSTR(" (Already listed)");
2732: else
2733: MSG_OUTSTR(" NOT FOUND");
2734: }
2735: }
2736: flushbuf(); /* output each line directly */
2737: }
2738:
2739: if (new_fname != NULL)
2740: {
2741: /* Push the new file onto the file stack */
2742: if (depth + 1 == old_files)
2743: {
2744: bigger = (SearchedFile *)lalloc(max_path_depth * 2
2745: * sizeof(SearchedFile), TRUE);
2746: if (bigger != NULL)
2747: {
2748: for (i = 0; i <= depth; i++)
2749: bigger[i] = files[i];
2750: for (i = depth + 1; i < old_files + max_path_depth; i++)
2751: {
2752: bigger[i].fp = NULL;
2753: bigger[i].name = NULL;
2754: bigger[i].lnum = 0;
2755: bigger[i].matched = FALSE;
2756: }
2757: for (i = old_files; i < max_path_depth; i++)
2758: bigger[i + max_path_depth] = files[i];
2759: old_files += max_path_depth;
2760: max_path_depth *= 2;
2761: vim_free(files);
2762: files = bigger;
2763: }
2764: }
2765: if ((files[depth + 1].fp = fopen((char *)new_fname, "r"))
2766: == NULL)
2767: vim_free(new_fname);
2768: else
2769: {
2770: if (++depth == old_files)
2771: {
2772: /*
2773: * lalloc() for 'bigger' must have failed above. We
2774: * will forget one of our already visited files now.
2775: */
2776: vim_free(files[old_files].name);
2777: ++old_files;
2778: }
2779: files[depth].name = curr_fname = new_fname;
2780: files[depth].lnum = 0;
2781: files[depth].matched = FALSE;
2782: }
2783: }
2784: }
2785: else
2786: {
2787: /*
2788: * Check if the line is a define (type == FIND_DEFINE)
2789: */
2790: p = line;
2791: define_matched = FALSE;
2792: if (define_prog != NULL && vim_regexec(define_prog, line, TRUE))
2793: {
2794: /*
2795: * Pattern must be first identifier after 'define', so skip
2796: * to that position before checking for match of pattern. Also
2797: * don't let it match beyond the end of this identifier.
2798: */
2799: p = define_prog->endp[0] + 1;
2800: while (*p && !isidchar(*p))
2801: p++;
2802: p2 = p;
2803: while (*p2 && isidchar(*p2))
2804: p2++;
2805: save_char = *p2;
2806: *p2 = NUL;
2807: define_matched = TRUE;
2808: }
2809:
2810: /*
2811: * Look for a match. Don't do this if we are looking for a
2812: * define and this line didn't match define_prog above.
2813: */
2814: if ((define_prog == NULL || define_matched) &&
2815: prog != NULL && vim_regexec(prog, p, p == line))
2816: {
2817: matched = TRUE;
2818: /*
2819: * Check if the line is not a comment line (unless we are
2820: * looking for a define). A line starting with "# define" is
2821: * not considered to be a comment line.
2822: */
2823: if (!define_matched && skip_comments)
2824: {
2825: fo_do_comments = TRUE;
2826: if ((*line != '#' ||
2827: STRNCMP(skipwhite(line + 1), "define", 6) != 0) &&
2828: get_leader_len(line, NULL))
2829: matched = FALSE;
2830:
2831: /*
2832: * Also check for a "/ *" or "/ /" before the match.
2833: * Skips lines like "int idx; / * normal index * /" when
2834: * looking for "normal".
2835: */
2836: else
2837: for (p = line; *p && p < prog->startp[0]; ++p)
2838: if (p[0] == '/' && (p[1] == '*' || p[1] == '/'))
2839: {
2840: matched = FALSE;
2841: break;
2842: }
2843: fo_do_comments = FALSE;
2844: }
2845: }
2846: if (define_matched)
2847: *p2 = save_char;
2848: }
2849: if (matched)
2850: {
2851: #ifdef INSERT_EXPAND
2852: if (action == ACTION_EXPAND)
2853: {
2854: if (depth == -1 && lnum == curwin->w_cursor.lnum)
2855: break;
2856: found = TRUE;
2857: p = prog->startp[0];
2858: while (iswordchar(*p))
2859: ++p;
2860: if (add_completion_and_infercase(prog->startp[0],
2861: (int)(p - prog->startp[0]),
2862: curr_fname == curbuf->b_xfilename ? NULL : curr_fname,
2863: FORWARD) == RET_ERROR)
2864: break;
2865: }
2866: else
2867: #endif
2868: if (action == ACTION_SHOW_ALL)
2869: {
2870: found = TRUE;
2871: if (!did_show)
2872: gotocmdline(TRUE); /* cursor at status line */
2873: if (curr_fname != prev_fname)
2874: {
2875: if (did_show)
2876: msg_outchar('\n'); /* cursor below last one */
2877: if (!got_int) /* don't display if 'q' typed
2878: at "--more--" mesage */
2879: {
2880: set_highlight('d'); /* Same as for directories */
2881: start_highlight();
2882: msg_home_replace(curr_fname);
2883: stop_highlight();
2884: }
2885: prev_fname = curr_fname;
2886: }
2887: did_show = TRUE;
2888: if (!got_int)
2889: show_pat_in_path(line, type, TRUE, action,
2890: (depth == -1) ? NULL : files[depth].fp,
2891: (depth == -1) ? &lnum : &files[depth].lnum,
2892: match_count++);
2893:
2894: /* Set matched flag for this file and all the ones that
2895: * include it */
2896: for (i = 0; i <= depth; ++i)
2897: files[i].matched = TRUE;
2898: }
2899: else if (--count <= 0)
2900: {
2901: found = TRUE;
2902: if (depth == -1 && lnum == curwin->w_cursor.lnum)
2903: EMSG("Match is on current line");
2904: else if (action == ACTION_SHOW)
2905: {
2906: show_pat_in_path(line, type, did_show, action,
2907: (depth == -1) ? NULL : files[depth].fp,
2908: (depth == -1) ? &lnum : &files[depth].lnum, 1L);
2909: did_show = TRUE;
2910: }
2911: else
2912: {
2913: if (action == ACTION_SPLIT)
2914: {
2915: if (win_split(0, FALSE) == FAIL)
2916: break;
2917: }
2918: if (depth == -1)
2919: {
2920: setpcmark();
2921: curwin->w_cursor.lnum = lnum;
2922: }
2923: else
2924: if (getfile(0, files[depth].name, NULL, TRUE,
2925: files[depth].lnum) > 0)
2926: break; /* failed to jump to file */
2927: }
2928: if (action != ACTION_SHOW)
2929: {
2930: curwin->w_cursor.col = prog->startp[0] - line;
2931: curwin->w_set_curswant = TRUE;
2932: }
2933: break;
2934: }
2935: matched = FALSE;
2936: }
2937: line_breakcheck();
2938: if (got_int)
2939: break;
2940: while (depth >= 0)
2941: {
2942: if (!vim_fgets(line = file_line, LSIZE, files[depth].fp))
2943: {
2944: ++files[depth].lnum;
2945: break;
2946: }
2947: fclose(files[depth].fp);
2948: --old_files;
2949: files[old_files].name = files[depth].name;
2950: files[old_files].matched = files[depth].matched;
2951: --depth;
2952: curr_fname = (depth == -1) ? curbuf->b_xfilename
2953: : files[depth].name;
2954: if (depth < depth_displayed)
2955: depth_displayed = depth;
2956: }
2957: if (depth < 0)
2958: {
2959: if (++lnum > end_lnum)
2960: break;
2961: line = ml_get(lnum);
2962: }
2963: }
2964: for (i = 0; i <= depth; i++)
2965: {
2966: fclose(files[i].fp);
2967: vim_free(files[i].name);
2968: }
2969: for (i = old_files; i < max_path_depth; i++)
2970: vim_free(files[i].name);
2971: vim_free(files);
2972:
2973: if (type == CHECK_PATH)
2974: {
2975: if (!did_show)
2976: {
2977: if (action != ACTION_SHOW_ALL)
2978: MSG("All included files were found");
2979: else
2980: MSG("No included files");
2981: }
2982: }
2983: else if (!found
2984: #ifdef INSERT_EXPAND
2985: && action != ACTION_EXPAND
2986: #endif
2987: )
2988: {
2989: if (got_int)
2990: emsg(e_interr);
2991: else if (type == FIND_DEFINE)
2992: EMSG("Couldn't find definition");
2993: else
2994: EMSG("Couldn't find pattern");
2995: }
2996: if (action == ACTION_SHOW || action == ACTION_SHOW_ALL)
2997: msg_end();
2998:
2999: fpip_end:
3000: vim_free(file_line);
3001: vim_free(prog);
3002: vim_free(include_prog);
3003: vim_free(define_prog);
3004: }
3005:
3006: static void
3007: show_pat_in_path(line, type, did_show, action, fp, lnum, count)
3008: char_u *line;
3009: int type;
3010: int did_show;
3011: int action;
3012: FILE *fp;
3013: linenr_t *lnum;
3014: long count;
3015: {
3016: char_u *p;
3017:
3018: if (did_show)
3019: msg_outchar('\n'); /* cursor below last one */
3020: else
3021: gotocmdline(TRUE); /* cursor at status line */
3022: if (got_int) /* 'q' typed at "--more--" message */
3023: return;
3024: for (;;)
3025: {
3026: p = line + STRLEN(line) - 1;
3027: if (fp != NULL)
3028: {
3029: /* We used fgets(), so get rid of newline at end */
3030: if (p >= line && *p == '\n')
3031: --p;
3032: if (p >= line && *p == '\r')
3033: --p;
3034: *(p + 1) = NUL;
3035: }
3036: if (action == ACTION_SHOW_ALL)
3037: {
3038: sprintf((char *)IObuff, "%3ld: ", count); /* show match nr */
3039: msg_outstr(IObuff);
3040: set_highlight('n'); /* Highlight line numbers */
3041: start_highlight();
3042: sprintf((char *)IObuff, "%4ld", *lnum); /* show line nr */
3043: msg_outstr(IObuff);
3044: stop_highlight();
3045: MSG_OUTSTR(" ");
3046: }
3047: msg_prt_line(line);
3048: flushbuf(); /* show one line at a time */
3049:
3050: /* Definition continues until line that doesn't end with '\' */
3051: if (got_int || type != FIND_DEFINE || p < line || *p != '\\')
3052: break;
3053:
3054: if (fp != NULL)
3055: {
3056: if (vim_fgets(line, LSIZE, fp)) /* end of file */
3057: break;
3058: ++*lnum;
3059: }
3060: else
3061: {
3062: if (++*lnum > curbuf->b_ml.ml_line_count)
3063: break;
3064: line = ml_get(*lnum);
3065: }
3066: msg_outchar('\n');
3067: }
3068: }
3069:
3070: #ifdef VIMINFO
3071: int
3072: read_viminfo_search_pattern(line, fp, force)
3073: char_u *line;
3074: FILE *fp;
3075: int force;
3076: {
3077: char_u *lp;
3078: char_u **pattern;
3079:
3080: lp = line;
3081: if (lp[0] == '~')
3082: lp++;
3083: if (lp[0] == '/')
3084: pattern = &search_pattern;
3085: else
3086: pattern = &subst_pattern;
3087: if (*pattern != NULL && force)
3088: vim_free(*pattern);
3089: if (force || *pattern == NULL)
3090: {
3091: viminfo_readstring(lp);
3092: *pattern = strsave(lp + 1);
3093: if (line[0] == '~')
3094: last_pattern = *pattern;
3095: }
3096: return vim_fgets(line, LSIZE, fp);
3097: }
3098:
3099: void
3100: write_viminfo_search_pattern(fp)
3101: FILE *fp;
3102: {
3103: if (get_viminfo_parameter('/') != 0)
3104: {
3105: if (search_pattern != NULL)
3106: {
3107: fprintf(fp, "\n# Last Search Pattern:\n");
3108: fprintf(fp, "%s/", (last_pattern == search_pattern) ? "~" : "");
3109: viminfo_writestring(fp, search_pattern);
3110: }
3111: if (subst_pattern != NULL)
3112: {
3113: fprintf(fp, "\n# Last Substitute Search Pattern:\n");
3114: fprintf(fp, "%s&", (last_pattern == subst_pattern) ? "~" : "");
3115: viminfo_writestring(fp, subst_pattern);
3116: }
3117: }
3118: }
3119: #endif /* VIMINFO */
3120:
3121: /*
3122: * Give a warning message.
3123: * Use 'w' highlighting and may repeat the message after redrawing
3124: */
3125: static void
3126: give_warning(message)
3127: char_u *message;
3128: {
3129: (void)set_highlight('w');
3130: msg_highlight = TRUE;
3131: if (msg(message) && !msg_scroll)
3132: {
3133: keep_msg = message;
3134: keep_msg_highlight = 'w';
3135: }
3136: msg_didout = FALSE; /* overwrite this message */
3137: msg_col = 0;
3138: }