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