1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | /* |
5 | * search.c: code for normal mode searching commands |
6 | */ |
7 | |
8 | #include <assert.h> |
9 | #include <inttypes.h> |
10 | #include <stdbool.h> |
11 | #include <string.h> |
12 | #include <limits.h> /* for INT_MAX on MSVC */ |
13 | |
14 | #include "nvim/ascii.h" |
15 | #include "nvim/vim.h" |
16 | #include "nvim/search.h" |
17 | #include "nvim/buffer.h" |
18 | #include "nvim/charset.h" |
19 | #include "nvim/cursor.h" |
20 | #include "nvim/edit.h" |
21 | #include "nvim/eval.h" |
22 | #include "nvim/ex_cmds.h" |
23 | #include "nvim/ex_cmds2.h" |
24 | #include "nvim/ex_getln.h" |
25 | #include "nvim/fileio.h" |
26 | #include "nvim/fold.h" |
27 | #include "nvim/func_attr.h" |
28 | #include "nvim/getchar.h" |
29 | #include "nvim/indent.h" |
30 | #include "nvim/main.h" |
31 | #include "nvim/mark.h" |
32 | #include "nvim/mbyte.h" |
33 | #include "nvim/memline.h" |
34 | #include "nvim/memory.h" |
35 | #include "nvim/message.h" |
36 | #include "nvim/misc1.h" |
37 | #include "nvim/move.h" |
38 | #include "nvim/mouse.h" |
39 | #include "nvim/normal.h" |
40 | #include "nvim/option.h" |
41 | #include "nvim/path.h" |
42 | #include "nvim/regexp.h" |
43 | #include "nvim/screen.h" |
44 | #include "nvim/strings.h" |
45 | #include "nvim/ui.h" |
46 | #include "nvim/window.h" |
47 | #include "nvim/os/time.h" |
48 | |
49 | |
50 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
51 | # include "search.c.generated.h" |
52 | #endif |
53 | /* |
54 | * This file contains various searching-related routines. These fall into |
55 | * three groups: |
56 | * 1. string searches (for /, ?, n, and N) |
57 | * 2. character searches within a single line (for f, F, t, T, etc) |
58 | * 3. "other" kinds of searches like the '%' command, and 'word' searches. |
59 | */ |
60 | |
61 | /* |
62 | * String searches |
63 | * |
64 | * The string search functions are divided into two levels: |
65 | * lowest: searchit(); uses a pos_T for starting position and found match. |
66 | * Highest: do_search(); uses curwin->w_cursor; calls searchit(). |
67 | * |
68 | * The last search pattern is remembered for repeating the same search. |
69 | * This pattern is shared between the :g, :s, ? and / commands. |
70 | * This is in search_regcomp(). |
71 | * |
72 | * The actual string matching is done using a heavily modified version of |
73 | * Henry Spencer's regular expression library. See regexp.c. |
74 | */ |
75 | |
76 | /* |
77 | * Two search patterns are remembered: One for the :substitute command and |
78 | * one for other searches. last_idx points to the one that was used the last |
79 | * time. |
80 | */ |
81 | static struct spat spats[2] = |
82 | { |
83 | // Last used search pattern |
84 | [0] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL}, |
85 | // Last used substitute pattern |
86 | [1] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL} |
87 | }; |
88 | |
89 | static int last_idx = 0; /* index in spats[] for RE_LAST */ |
90 | |
91 | static char_u lastc[2] = { NUL, NUL }; // last character searched for |
92 | static int lastcdir = FORWARD; // last direction of character search |
93 | static int last_t_cmd = true; // last search t_cmd |
94 | static char_u lastc_bytes[MB_MAXBYTES + 1]; |
95 | static int lastc_bytelen = 1; // >1 for multi-byte char |
96 | |
97 | // copy of spats[], for keeping the search patterns while executing autocmds |
98 | static struct spat saved_spats[2]; |
99 | // copy of spats[RE_SEARCH], for keeping the search patterns while incremental |
100 | // searching |
101 | static struct spat saved_last_search_spat; |
102 | static int saved_last_idx = 0; |
103 | static bool saved_no_hlsearch = false; |
104 | |
105 | static char_u *mr_pattern = NULL; // pattern used by search_regcomp() |
106 | static int mr_pattern_alloced = false; // mr_pattern was allocated |
107 | |
108 | /* |
109 | * Type used by find_pattern_in_path() to remember which included files have |
110 | * been searched already. |
111 | */ |
112 | typedef struct SearchedFile { |
113 | FILE *fp; /* File pointer */ |
114 | char_u *name; /* Full name of file */ |
115 | linenr_T lnum; /* Line we were up to in file */ |
116 | int matched; /* Found a match in this file */ |
117 | } SearchedFile; |
118 | |
119 | /* |
120 | * translate search pattern for vim_regcomp() |
121 | * |
122 | * pat_save == RE_SEARCH: save pat in spats[RE_SEARCH].pat (normal search cmd) |
123 | * pat_save == RE_SUBST: save pat in spats[RE_SUBST].pat (:substitute command) |
124 | * pat_save == RE_BOTH: save pat in both patterns (:global command) |
125 | * pat_use == RE_SEARCH: use previous search pattern if "pat" is NULL |
126 | * pat_use == RE_SUBST: use previous substitute pattern if "pat" is NULL |
127 | * pat_use == RE_LAST: use last used pattern if "pat" is NULL |
128 | * options & SEARCH_HIS: put search string in history |
129 | * options & SEARCH_KEEP: keep previous search pattern |
130 | * |
131 | * returns FAIL if failed, OK otherwise. |
132 | */ |
133 | int |
134 | search_regcomp( |
135 | char_u *pat, |
136 | int pat_save, |
137 | int pat_use, |
138 | int options, |
139 | regmmatch_T *regmatch /* return: pattern and ignore-case flag */ |
140 | ) |
141 | { |
142 | int magic; |
143 | int i; |
144 | |
145 | rc_did_emsg = FALSE; |
146 | magic = p_magic; |
147 | |
148 | /* |
149 | * If no pattern given, use a previously defined pattern. |
150 | */ |
151 | if (pat == NULL || *pat == NUL) { |
152 | if (pat_use == RE_LAST) |
153 | i = last_idx; |
154 | else |
155 | i = pat_use; |
156 | if (spats[i].pat == NULL) { /* pattern was never defined */ |
157 | if (pat_use == RE_SUBST) |
158 | EMSG(_(e_nopresub)); |
159 | else |
160 | EMSG(_(e_noprevre)); |
161 | rc_did_emsg = TRUE; |
162 | return FAIL; |
163 | } |
164 | pat = spats[i].pat; |
165 | magic = spats[i].magic; |
166 | no_smartcase = spats[i].no_scs; |
167 | } else if (options & SEARCH_HIS) /* put new pattern in history */ |
168 | add_to_history(HIST_SEARCH, pat, TRUE, NUL); |
169 | |
170 | if (mr_pattern_alloced) { |
171 | xfree(mr_pattern); |
172 | mr_pattern_alloced = FALSE; |
173 | } |
174 | |
175 | if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { |
176 | mr_pattern = reverse_text(pat); |
177 | mr_pattern_alloced = TRUE; |
178 | } else |
179 | mr_pattern = pat; |
180 | |
181 | /* |
182 | * Save the currently used pattern in the appropriate place, |
183 | * unless the pattern should not be remembered. |
184 | */ |
185 | if (!(options & SEARCH_KEEP) && !cmdmod.keeppatterns) { |
186 | /* search or global command */ |
187 | if (pat_save == RE_SEARCH || pat_save == RE_BOTH) |
188 | save_re_pat(RE_SEARCH, pat, magic); |
189 | /* substitute or global command */ |
190 | if (pat_save == RE_SUBST || pat_save == RE_BOTH) |
191 | save_re_pat(RE_SUBST, pat, magic); |
192 | } |
193 | |
194 | regmatch->rmm_ic = ignorecase(pat); |
195 | regmatch->rmm_maxcol = 0; |
196 | regmatch->regprog = vim_regcomp(pat, magic ? RE_MAGIC : 0); |
197 | if (regmatch->regprog == NULL) |
198 | return FAIL; |
199 | return OK; |
200 | } |
201 | |
202 | /* |
203 | * Get search pattern used by search_regcomp(). |
204 | */ |
205 | char_u *get_search_pat(void) |
206 | { |
207 | return mr_pattern; |
208 | } |
209 | |
210 | /* |
211 | * Reverse text into allocated memory. |
212 | * Returns the allocated string. |
213 | * |
214 | * TODO(philix): move reverse_text() to strings.c |
215 | */ |
216 | char_u *reverse_text(char_u *s) FUNC_ATTR_NONNULL_RET |
217 | { |
218 | /* |
219 | * Reverse the pattern. |
220 | */ |
221 | size_t len = STRLEN(s); |
222 | char_u *rev = xmalloc(len + 1); |
223 | size_t rev_i = len; |
224 | for (size_t s_i = 0; s_i < len; ++s_i) { |
225 | if (has_mbyte) { |
226 | int mb_len = (*mb_ptr2len)(s + s_i); |
227 | rev_i -= mb_len; |
228 | memmove(rev + rev_i, s + s_i, mb_len); |
229 | s_i += mb_len - 1; |
230 | } else |
231 | rev[--rev_i] = s[s_i]; |
232 | } |
233 | rev[len] = NUL; |
234 | |
235 | return rev; |
236 | } |
237 | |
238 | void save_re_pat(int idx, char_u *pat, int magic) |
239 | { |
240 | if (spats[idx].pat != pat) { |
241 | free_spat(&spats[idx]); |
242 | spats[idx].pat = vim_strsave(pat); |
243 | spats[idx].magic = magic; |
244 | spats[idx].no_scs = no_smartcase; |
245 | spats[idx].timestamp = os_time(); |
246 | spats[idx].additional_data = NULL; |
247 | last_idx = idx; |
248 | /* If 'hlsearch' set and search pat changed: need redraw. */ |
249 | if (p_hls) |
250 | redraw_all_later(SOME_VALID); |
251 | set_no_hlsearch(false); |
252 | } |
253 | } |
254 | |
255 | /* |
256 | * Save the search patterns, so they can be restored later. |
257 | * Used before/after executing autocommands and user functions. |
258 | */ |
259 | static int save_level = 0; |
260 | |
261 | void save_search_patterns(void) |
262 | { |
263 | if (save_level++ == 0) { |
264 | saved_spats[0] = spats[0]; |
265 | if (spats[0].pat != NULL) |
266 | saved_spats[0].pat = vim_strsave(spats[0].pat); |
267 | saved_spats[1] = spats[1]; |
268 | if (spats[1].pat != NULL) |
269 | saved_spats[1].pat = vim_strsave(spats[1].pat); |
270 | saved_last_idx = last_idx; |
271 | saved_no_hlsearch = no_hlsearch; |
272 | } |
273 | } |
274 | |
275 | void restore_search_patterns(void) |
276 | { |
277 | if (--save_level == 0) { |
278 | free_spat(&spats[0]); |
279 | spats[0] = saved_spats[0]; |
280 | set_vv_searchforward(); |
281 | free_spat(&spats[1]); |
282 | spats[1] = saved_spats[1]; |
283 | last_idx = saved_last_idx; |
284 | set_no_hlsearch(saved_no_hlsearch); |
285 | } |
286 | } |
287 | |
288 | static inline void free_spat(struct spat *const spat) |
289 | { |
290 | xfree(spat->pat); |
291 | tv_dict_unref(spat->additional_data); |
292 | } |
293 | |
294 | #if defined(EXITFREE) |
295 | void free_search_patterns(void) |
296 | { |
297 | free_spat(&spats[0]); |
298 | free_spat(&spats[1]); |
299 | |
300 | memset(spats, 0, sizeof(spats)); |
301 | |
302 | if (mr_pattern_alloced) { |
303 | xfree(mr_pattern); |
304 | mr_pattern_alloced = FALSE; |
305 | mr_pattern = NULL; |
306 | } |
307 | } |
308 | |
309 | #endif |
310 | |
311 | /// Save and restore the search pattern for incremental highlight search |
312 | /// feature. |
313 | /// |
314 | /// It's similar to but different from save_search_patterns() and |
315 | /// restore_search_patterns(), because the search pattern must be restored when |
316 | /// cancelling incremental searching even if it's called inside user functions. |
317 | void save_last_search_pattern(void) |
318 | { |
319 | saved_last_search_spat = spats[RE_SEARCH]; |
320 | if (spats[RE_SEARCH].pat != NULL) { |
321 | saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat); |
322 | } |
323 | saved_last_idx = last_idx; |
324 | saved_no_hlsearch = no_hlsearch; |
325 | } |
326 | |
327 | void restore_last_search_pattern(void) |
328 | { |
329 | xfree(spats[RE_SEARCH].pat); |
330 | spats[RE_SEARCH] = saved_last_search_spat; |
331 | set_vv_searchforward(); |
332 | last_idx = saved_last_idx; |
333 | set_no_hlsearch(saved_no_hlsearch); |
334 | } |
335 | |
336 | char_u *last_search_pattern(void) |
337 | { |
338 | return spats[RE_SEARCH].pat; |
339 | } |
340 | |
341 | /* |
342 | * Return TRUE when case should be ignored for search pattern "pat". |
343 | * Uses the 'ignorecase' and 'smartcase' options. |
344 | */ |
345 | int ignorecase(char_u *pat) |
346 | { |
347 | return ignorecase_opt(pat, p_ic, p_scs); |
348 | } |
349 | |
350 | /// As ignorecase() put pass the "ic" and "scs" flags. |
351 | int ignorecase_opt(char_u *pat, int ic_in, int scs) |
352 | { |
353 | int ic = ic_in; |
354 | if (ic && !no_smartcase && scs |
355 | && !(ctrl_x_mode_not_default() && curbuf->b_p_inf) |
356 | ) { |
357 | ic = !pat_has_uppercase(pat); |
358 | } |
359 | no_smartcase = false; |
360 | |
361 | return ic; |
362 | } |
363 | |
364 | /// Returns true if pattern `pat` has an uppercase character. |
365 | bool pat_has_uppercase(char_u *pat) |
366 | FUNC_ATTR_NONNULL_ALL |
367 | { |
368 | char_u *p = pat; |
369 | |
370 | while (*p != NUL) { |
371 | const int l = mb_ptr2len(p); |
372 | |
373 | if (l > 1) { |
374 | if (mb_isupper(utf_ptr2char(p))) { |
375 | return true; |
376 | } |
377 | p += l; |
378 | } else if (*p == '\\') { |
379 | if (p[1] == '_' && p[2] != NUL) { // skip "\_X" |
380 | p += 3; |
381 | } else if (p[1] == '%' && p[2] != NUL) { // skip "\%X" |
382 | p += 3; |
383 | } else if (p[1] != NUL) { // skip "\X" |
384 | p += 2; |
385 | } else { |
386 | p += 1; |
387 | } |
388 | } else if (mb_isupper(*p)) { |
389 | return true; |
390 | } else { |
391 | p++; |
392 | } |
393 | } |
394 | return false; |
395 | } |
396 | |
397 | const char *last_csearch(void) |
398 | FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT |
399 | { |
400 | return (const char *)lastc_bytes; |
401 | } |
402 | |
403 | int last_csearch_forward(void) |
404 | { |
405 | return lastcdir == FORWARD; |
406 | } |
407 | |
408 | int last_csearch_until(void) |
409 | { |
410 | return last_t_cmd == TRUE; |
411 | } |
412 | |
413 | void set_last_csearch(int c, char_u *s, int len) |
414 | { |
415 | *lastc = c; |
416 | lastc_bytelen = len; |
417 | if (len) |
418 | memcpy(lastc_bytes, s, len); |
419 | else |
420 | memset(lastc_bytes, 0, sizeof(lastc_bytes)); |
421 | } |
422 | |
423 | void set_csearch_direction(int cdir) |
424 | { |
425 | lastcdir = cdir; |
426 | } |
427 | |
428 | void set_csearch_until(int t_cmd) |
429 | { |
430 | last_t_cmd = t_cmd; |
431 | } |
432 | |
433 | char_u *last_search_pat(void) |
434 | { |
435 | return spats[last_idx].pat; |
436 | } |
437 | |
438 | /* |
439 | * Reset search direction to forward. For "gd" and "gD" commands. |
440 | */ |
441 | void reset_search_dir(void) |
442 | { |
443 | spats[0].off.dir = '/'; |
444 | set_vv_searchforward(); |
445 | } |
446 | |
447 | /* |
448 | * Set the last search pattern. For ":let @/ =" and ShaDa file. |
449 | * Also set the saved search pattern, so that this works in an autocommand. |
450 | */ |
451 | void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) |
452 | { |
453 | free_spat(&spats[idx]); |
454 | /* An empty string means that nothing should be matched. */ |
455 | if (*s == NUL) |
456 | spats[idx].pat = NULL; |
457 | else |
458 | spats[idx].pat = (char_u *) xstrdup((char *) s); |
459 | spats[idx].timestamp = os_time(); |
460 | spats[idx].additional_data = NULL; |
461 | spats[idx].magic = magic; |
462 | spats[idx].no_scs = FALSE; |
463 | spats[idx].off.dir = '/'; |
464 | set_vv_searchforward(); |
465 | spats[idx].off.line = FALSE; |
466 | spats[idx].off.end = FALSE; |
467 | spats[idx].off.off = 0; |
468 | if (setlast) |
469 | last_idx = idx; |
470 | if (save_level) { |
471 | free_spat(&saved_spats[idx]); |
472 | saved_spats[idx] = spats[0]; |
473 | if (spats[idx].pat == NULL) |
474 | saved_spats[idx].pat = NULL; |
475 | else |
476 | saved_spats[idx].pat = vim_strsave(spats[idx].pat); |
477 | saved_last_idx = last_idx; |
478 | } |
479 | /* If 'hlsearch' set and search pat changed: need redraw. */ |
480 | if (p_hls && idx == last_idx && !no_hlsearch) |
481 | redraw_all_later(SOME_VALID); |
482 | } |
483 | |
484 | /* |
485 | * Get a regexp program for the last used search pattern. |
486 | * This is used for highlighting all matches in a window. |
487 | * Values returned in regmatch->regprog and regmatch->rmm_ic. |
488 | */ |
489 | void last_pat_prog(regmmatch_T *regmatch) |
490 | { |
491 | if (spats[last_idx].pat == NULL) { |
492 | regmatch->regprog = NULL; |
493 | return; |
494 | } |
495 | ++emsg_off; /* So it doesn't beep if bad expr */ |
496 | (void)search_regcomp((char_u *)"" , 0, last_idx, SEARCH_KEEP, regmatch); |
497 | --emsg_off; |
498 | } |
499 | |
500 | /// lowest level search function. |
501 | /// Search for 'count'th occurrence of pattern "pat" in direction "dir". |
502 | /// Start at position "pos" and return the found position in "pos". |
503 | /// |
504 | /// if (options & SEARCH_MSG) == 0 don't give any messages |
505 | /// if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages |
506 | /// if (options & SEARCH_MSG) == SEARCH_MSG give all messages |
507 | /// if (options & SEARCH_HIS) put search pattern in history |
508 | /// if (options & SEARCH_END) return position at end of match |
509 | /// if (options & SEARCH_START) accept match at pos itself |
510 | /// if (options & SEARCH_KEEP) keep previous search pattern |
511 | /// if (options & SEARCH_FOLD) match only once in a closed fold |
512 | /// if (options & SEARCH_PEEK) check for typed char, cancel search |
513 | /// if (options & SEARCH_COL) start at pos->col instead of zero |
514 | /// |
515 | /// @returns FAIL (zero) for failure, non-zero for success. |
516 | /// the index of the first matching |
517 | /// subpattern plus one; one if there was none. |
518 | int searchit( |
519 | win_T *win, /* window to search in, can be NULL for a |
520 | buffer without a window! */ |
521 | buf_T *buf, |
522 | pos_T *pos, |
523 | pos_T *end_pos, // set to end of the match, unless NULL |
524 | Direction dir, |
525 | char_u *pat, |
526 | long count, |
527 | int options, |
528 | int pat_use, // which pattern to use when "pat" is empty |
529 | linenr_T stop_lnum, // stop after this line number when != 0 |
530 | proftime_T *tm, // timeout limit or NULL |
531 | int *timed_out // set when timed out or NULL |
532 | ) |
533 | { |
534 | int found; |
535 | linenr_T lnum; /* no init to shut up Apollo cc */ |
536 | regmmatch_T regmatch; |
537 | char_u *ptr; |
538 | colnr_T matchcol; |
539 | lpos_T endpos; |
540 | lpos_T matchpos; |
541 | int loop; |
542 | pos_T start_pos; |
543 | int at_first_line; |
544 | int ; |
545 | int start_char_len; |
546 | int match_ok; |
547 | long nmatched; |
548 | int submatch = 0; |
549 | bool first_match = true; |
550 | int save_called_emsg = called_emsg; |
551 | int break_loop = FALSE; |
552 | |
553 | if (search_regcomp(pat, RE_SEARCH, pat_use, |
554 | (options & (SEARCH_HIS + SEARCH_KEEP)), ®match) == FAIL) { |
555 | if ((options & SEARCH_MSG) && !rc_did_emsg) |
556 | EMSG2(_("E383: Invalid search string: %s" ), mr_pattern); |
557 | return FAIL; |
558 | } |
559 | |
560 | /* |
561 | * find the string |
562 | */ |
563 | called_emsg = FALSE; |
564 | do { /* loop for count */ |
565 | // When not accepting a match at the start position set "extra_col" to a |
566 | // non-zero value. Don't do that when starting at MAXCOL, since MAXCOL + 1 |
567 | // is zero. |
568 | if (pos->col == MAXCOL) { |
569 | start_char_len = 0; |
570 | } else if (has_mbyte |
571 | && pos->lnum >= 1 && pos->lnum <= buf->b_ml.ml_line_count |
572 | && pos->col < MAXCOL - 2) { |
573 | // Watch out for the "col" being MAXCOL - 2, used in a closed fold. |
574 | ptr = ml_get_buf(buf, pos->lnum, false); |
575 | if ((int)STRLEN(ptr) <= pos->col) { |
576 | start_char_len = 1; |
577 | } else { |
578 | start_char_len = utfc_ptr2len(ptr + pos->col); |
579 | } |
580 | } else { |
581 | start_char_len = 1; |
582 | } |
583 | if (dir == FORWARD) { |
584 | extra_col = (options & SEARCH_START) ? 0 : start_char_len; |
585 | } else { |
586 | extra_col = (options & SEARCH_START) ? start_char_len : 0; |
587 | } |
588 | |
589 | start_pos = *pos; /* remember start pos for detecting no match */ |
590 | found = 0; /* default: not found */ |
591 | at_first_line = TRUE; /* default: start in first line */ |
592 | if (pos->lnum == 0) { /* correct lnum for when starting in line 0 */ |
593 | pos->lnum = 1; |
594 | pos->col = 0; |
595 | at_first_line = FALSE; /* not in first line now */ |
596 | } |
597 | |
598 | /* |
599 | * Start searching in current line, unless searching backwards and |
600 | * we're in column 0. |
601 | * If we are searching backwards, in column 0, and not including the |
602 | * current position, gain some efficiency by skipping back a line. |
603 | * Otherwise begin the search in the current line. |
604 | */ |
605 | if (dir == BACKWARD && start_pos.col == 0 |
606 | && (options & SEARCH_START) == 0) { |
607 | lnum = pos->lnum - 1; |
608 | at_first_line = FALSE; |
609 | } else |
610 | lnum = pos->lnum; |
611 | |
612 | for (loop = 0; loop <= 1; ++loop) { /* loop twice if 'wrapscan' set */ |
613 | for (; lnum > 0 && lnum <= buf->b_ml.ml_line_count; |
614 | lnum += dir, at_first_line = FALSE) { |
615 | /* Stop after checking "stop_lnum", if it's set. */ |
616 | if (stop_lnum != 0 && (dir == FORWARD |
617 | ? lnum > stop_lnum : lnum < stop_lnum)) |
618 | break; |
619 | /* Stop after passing the "tm" time limit. */ |
620 | if (tm != NULL && profile_passed_limit(*tm)) |
621 | break; |
622 | |
623 | // Look for a match somewhere in line "lnum". |
624 | colnr_T col = at_first_line && (options & SEARCH_COL) ? pos->col : 0; |
625 | nmatched = vim_regexec_multi(®match, win, buf, |
626 | lnum, col, tm, timed_out); |
627 | // Abort searching on an error (e.g., out of stack). |
628 | if (called_emsg || (timed_out != NULL && *timed_out)) { |
629 | break; |
630 | } |
631 | if (nmatched > 0) { |
632 | /* match may actually be in another line when using \zs */ |
633 | matchpos = regmatch.startpos[0]; |
634 | endpos = regmatch.endpos[0]; |
635 | submatch = first_submatch(®match); |
636 | /* "lnum" may be past end of buffer for "\n\zs". */ |
637 | if (lnum + matchpos.lnum > buf->b_ml.ml_line_count) |
638 | ptr = (char_u *)"" ; |
639 | else |
640 | ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE); |
641 | |
642 | /* |
643 | * Forward search in the first line: match should be after |
644 | * the start position. If not, continue at the end of the |
645 | * match (this is vi compatible) or on the next char. |
646 | */ |
647 | if (dir == FORWARD && at_first_line) { |
648 | match_ok = TRUE; |
649 | /* |
650 | * When the match starts in a next line it's certainly |
651 | * past the start position. |
652 | * When match lands on a NUL the cursor will be put |
653 | * one back afterwards, compare with that position, |
654 | * otherwise "/$" will get stuck on end of line. |
655 | */ |
656 | while (matchpos.lnum == 0 |
657 | && (((options & SEARCH_END) && first_match) |
658 | ? (nmatched == 1 |
659 | && (int)endpos.col - 1 |
660 | < (int)start_pos.col + extra_col) |
661 | : ((int)matchpos.col |
662 | - (ptr[matchpos.col] == NUL) |
663 | < (int)start_pos.col + extra_col))) { |
664 | /* |
665 | * If vi-compatible searching, continue at the end |
666 | * of the match, otherwise continue one position |
667 | * forward. |
668 | */ |
669 | if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) { |
670 | if (nmatched > 1) { |
671 | /* end is in next line, thus no match in |
672 | * this line */ |
673 | match_ok = FALSE; |
674 | break; |
675 | } |
676 | matchcol = endpos.col; |
677 | // for empty match (matchcol == matchpos.col): advance one char |
678 | } else { |
679 | // Prepare to start after first matched character. |
680 | matchcol = matchpos.col; |
681 | } |
682 | |
683 | if (matchcol == matchpos.col && ptr[matchcol] != NUL) { |
684 | matchcol += MB_PTR2LEN(ptr + matchcol); |
685 | } |
686 | |
687 | if (matchcol == 0 && (options & SEARCH_START)) { |
688 | break; |
689 | } |
690 | |
691 | if (ptr[matchcol] == NUL |
692 | || (nmatched = vim_regexec_multi(®match, win, buf, |
693 | lnum, matchcol, tm, |
694 | timed_out)) == 0) { |
695 | match_ok = false; |
696 | break; |
697 | } |
698 | matchpos = regmatch.startpos[0]; |
699 | endpos = regmatch.endpos[0]; |
700 | submatch = first_submatch(®match); |
701 | |
702 | // This while-loop only works with matchpos.lnum == 0. |
703 | // For bigger values the next line pointer ptr might not be a |
704 | // buffer line. |
705 | if (matchpos.lnum != 0) { |
706 | break; |
707 | } |
708 | // Need to get the line pointer again, a multi-line search may |
709 | // have made it invalid. |
710 | ptr = ml_get_buf(buf, lnum, false); |
711 | } |
712 | if (!match_ok) |
713 | continue; |
714 | } |
715 | if (dir == BACKWARD) { |
716 | /* |
717 | * Now, if there are multiple matches on this line, |
718 | * we have to get the last one. Or the last one before |
719 | * the cursor, if we're on that line. |
720 | * When putting the new cursor at the end, compare |
721 | * relative to the end of the match. |
722 | */ |
723 | match_ok = FALSE; |
724 | for (;; ) { |
725 | /* Remember a position that is before the start |
726 | * position, we use it if it's the last match in |
727 | * the line. Always accept a position after |
728 | * wrapping around. */ |
729 | if (loop |
730 | || ((options & SEARCH_END) |
731 | ? (lnum + regmatch.endpos[0].lnum |
732 | < start_pos.lnum |
733 | || (lnum + regmatch.endpos[0].lnum |
734 | == start_pos.lnum |
735 | && (int)regmatch.endpos[0].col - 1 |
736 | < (int)start_pos.col + extra_col)) |
737 | : (lnum + regmatch.startpos[0].lnum |
738 | < start_pos.lnum |
739 | || (lnum + regmatch.startpos[0].lnum |
740 | == start_pos.lnum |
741 | && (int)regmatch.startpos[0].col |
742 | < (int)start_pos.col + extra_col)))) { |
743 | match_ok = true; |
744 | matchpos = regmatch.startpos[0]; |
745 | endpos = regmatch.endpos[0]; |
746 | submatch = first_submatch(®match); |
747 | } else |
748 | break; |
749 | |
750 | // We found a valid match, now check if there is |
751 | // another one after it. |
752 | // If vi-compatible searching, continue at the end |
753 | // of the match, otherwise continue one position |
754 | // forward. |
755 | if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) { |
756 | if (nmatched > 1) { |
757 | break; |
758 | } |
759 | matchcol = endpos.col; |
760 | // for empty match: advance one char |
761 | if (matchcol == matchpos.col |
762 | && ptr[matchcol] != NUL) { |
763 | matchcol += mb_ptr2len(ptr + matchcol); |
764 | } |
765 | } else { |
766 | // Stop when the match is in a next line. |
767 | if (matchpos.lnum > 0) { |
768 | break; |
769 | } |
770 | matchcol = matchpos.col; |
771 | if (ptr[matchcol] != NUL) { |
772 | matchcol += mb_ptr2len(ptr + matchcol); |
773 | } |
774 | } |
775 | if (ptr[matchcol] == NUL |
776 | || (nmatched = vim_regexec_multi( |
777 | ®match, win, buf, lnum + matchpos.lnum, matchcol, |
778 | tm, timed_out)) == 0) { |
779 | // If the search timed out, we did find a match |
780 | // but it might be the wrong one, so that's not |
781 | // OK. |
782 | if (tm != NULL && profile_passed_limit(*tm)) { |
783 | match_ok = false; |
784 | } |
785 | break; |
786 | } |
787 | |
788 | /* Need to get the line pointer again, a |
789 | * multi-line search may have made it invalid. */ |
790 | ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE); |
791 | } |
792 | |
793 | /* |
794 | * If there is only a match after the cursor, skip |
795 | * this match. |
796 | */ |
797 | if (!match_ok) |
798 | continue; |
799 | } |
800 | |
801 | /* With the SEARCH_END option move to the last character |
802 | * of the match. Don't do it for an empty match, end |
803 | * should be same as start then. */ |
804 | if ((options & SEARCH_END) && !(options & SEARCH_NOOF) |
805 | && !(matchpos.lnum == endpos.lnum |
806 | && matchpos.col == endpos.col)) { |
807 | /* For a match in the first column, set the position |
808 | * on the NUL in the previous line. */ |
809 | pos->lnum = lnum + endpos.lnum; |
810 | pos->col = endpos.col; |
811 | if (endpos.col == 0) { |
812 | if (pos->lnum > 1) { /* just in case */ |
813 | --pos->lnum; |
814 | pos->col = (colnr_T)STRLEN(ml_get_buf(buf, |
815 | pos->lnum, FALSE)); |
816 | } |
817 | } else { |
818 | pos->col--; |
819 | if (pos->lnum <= buf->b_ml.ml_line_count) { |
820 | ptr = ml_get_buf(buf, pos->lnum, false); |
821 | pos->col -= utf_head_off(ptr, ptr + pos->col); |
822 | } |
823 | } |
824 | if (end_pos != NULL) { |
825 | end_pos->lnum = lnum + matchpos.lnum; |
826 | end_pos->col = matchpos.col; |
827 | } |
828 | } else { |
829 | pos->lnum = lnum + matchpos.lnum; |
830 | pos->col = matchpos.col; |
831 | if (end_pos != NULL) { |
832 | end_pos->lnum = lnum + endpos.lnum; |
833 | end_pos->col = endpos.col; |
834 | } |
835 | } |
836 | pos->coladd = 0; |
837 | if (end_pos != NULL) { |
838 | end_pos->coladd = 0; |
839 | } |
840 | found = 1; |
841 | first_match = false; |
842 | |
843 | /* Set variables used for 'incsearch' highlighting. */ |
844 | search_match_lines = endpos.lnum - matchpos.lnum; |
845 | search_match_endcol = endpos.col; |
846 | break; |
847 | } |
848 | line_breakcheck(); /* stop if ctrl-C typed */ |
849 | if (got_int) |
850 | break; |
851 | |
852 | /* Cancel searching if a character was typed. Used for |
853 | * 'incsearch'. Don't check too often, that would slowdown |
854 | * searching too much. */ |
855 | if ((options & SEARCH_PEEK) |
856 | && ((lnum - pos->lnum) & 0x3f) == 0 |
857 | && char_avail()) { |
858 | break_loop = TRUE; |
859 | break; |
860 | } |
861 | |
862 | if (loop && lnum == start_pos.lnum) |
863 | break; /* if second loop, stop where started */ |
864 | } |
865 | at_first_line = FALSE; |
866 | |
867 | // Stop the search if wrapscan isn't set, "stop_lnum" is |
868 | // specified, after an interrupt, after a match and after looping |
869 | // twice. |
870 | if (!p_ws || stop_lnum != 0 || got_int || called_emsg |
871 | || (timed_out != NULL && *timed_out) |
872 | || break_loop |
873 | || found || loop) { |
874 | break; |
875 | } |
876 | // |
877 | // If 'wrapscan' is set we continue at the other end of the file. |
878 | // If 'shortmess' does not contain 's', we give a message. |
879 | // This message is also remembered in keep_msg for when the screen |
880 | // is redrawn. The keep_msg is cleared whenever another message is |
881 | // written. |
882 | // |
883 | if (dir == BACKWARD) { // start second loop at the other end |
884 | lnum = buf->b_ml.ml_line_count; |
885 | } else { |
886 | lnum = 1; |
887 | } |
888 | if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG)) { |
889 | give_warning((char_u *)_(dir == BACKWARD |
890 | ? top_bot_msg : bot_top_msg), true); |
891 | } |
892 | } |
893 | if (got_int || called_emsg |
894 | || (timed_out != NULL && *timed_out) |
895 | || break_loop |
896 | ) { |
897 | break; |
898 | } |
899 | } while (--count > 0 && found); // stop after count matches or no match |
900 | |
901 | vim_regfree(regmatch.regprog); |
902 | |
903 | called_emsg |= save_called_emsg; |
904 | |
905 | if (!found) { /* did not find it */ |
906 | if (got_int) |
907 | EMSG(_(e_interr)); |
908 | else if ((options & SEARCH_MSG) == SEARCH_MSG) { |
909 | if (p_ws) |
910 | EMSG2(_(e_patnotf2), mr_pattern); |
911 | else if (lnum == 0) |
912 | EMSG2(_("E384: search hit TOP without match for: %s" ), |
913 | mr_pattern); |
914 | else |
915 | EMSG2(_("E385: search hit BOTTOM without match for: %s" ), |
916 | mr_pattern); |
917 | } |
918 | return FAIL; |
919 | } |
920 | |
921 | /* A pattern like "\n\zs" may go past the last line. */ |
922 | if (pos->lnum > buf->b_ml.ml_line_count) { |
923 | pos->lnum = buf->b_ml.ml_line_count; |
924 | pos->col = (int)STRLEN(ml_get_buf(buf, pos->lnum, FALSE)); |
925 | if (pos->col > 0) |
926 | --pos->col; |
927 | } |
928 | |
929 | return submatch + 1; |
930 | } |
931 | |
932 | void set_search_direction(int cdir) |
933 | { |
934 | spats[0].off.dir = cdir; |
935 | } |
936 | |
937 | static void set_vv_searchforward(void) |
938 | { |
939 | set_vim_var_nr(VV_SEARCHFORWARD, (long)(spats[0].off.dir == '/')); |
940 | } |
941 | |
942 | // Return the number of the first subpat that matched. |
943 | // Return zero if none of them matched. |
944 | static int first_submatch(regmmatch_T *rp) |
945 | { |
946 | int submatch; |
947 | |
948 | for (submatch = 1;; ++submatch) { |
949 | if (rp->startpos[submatch].lnum >= 0) |
950 | break; |
951 | if (submatch == 9) { |
952 | submatch = 0; |
953 | break; |
954 | } |
955 | } |
956 | return submatch; |
957 | } |
958 | |
959 | /* |
960 | * Highest level string search function. |
961 | * Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc' |
962 | * If 'dirc' is 0: use previous dir. |
963 | * If 'pat' is NULL or empty : use previous string. |
964 | * If 'options & SEARCH_REV' : go in reverse of previous dir. |
965 | * If 'options & SEARCH_ECHO': echo the search command and handle options |
966 | * If 'options & SEARCH_MSG' : may give error message |
967 | * If 'options & SEARCH_OPT' : interpret optional flags |
968 | * If 'options & SEARCH_HIS' : put search pattern in history |
969 | * If 'options & SEARCH_NOOF': don't add offset to position |
970 | * If 'options & SEARCH_MARK': set previous context mark |
971 | * If 'options & SEARCH_KEEP': keep previous search pattern |
972 | * If 'options & SEARCH_START': accept match at curpos itself |
973 | * If 'options & SEARCH_PEEK': check for typed char, cancel search |
974 | * |
975 | * Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this |
976 | * makes the movement linewise without moving the match position. |
977 | * |
978 | * Return 0 for failure, 1 for found, 2 for found and line offset added. |
979 | */ |
980 | int do_search( |
981 | oparg_T *oap, /* can be NULL */ |
982 | int dirc, /* '/' or '?' */ |
983 | char_u *pat, |
984 | long count, |
985 | int options, |
986 | proftime_T *tm, // timeout limit or NULL |
987 | int *timed_out // flag set on timeout or NULL |
988 | ) |
989 | { |
990 | pos_T pos; /* position of the last match */ |
991 | char_u *searchstr; |
992 | struct soffset old_off; |
993 | int retval; /* Return value */ |
994 | char_u *p; |
995 | long c; |
996 | char_u *dircp; |
997 | char_u *strcopy = NULL; |
998 | char_u *ps; |
999 | char_u *msgbuf = NULL; |
1000 | size_t len; |
1001 | bool has_offset = false; |
1002 | #define SEARCH_STAT_BUF_LEN 12 |
1003 | |
1004 | /* |
1005 | * A line offset is not remembered, this is vi compatible. |
1006 | */ |
1007 | if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL) { |
1008 | spats[0].off.line = FALSE; |
1009 | spats[0].off.off = 0; |
1010 | } |
1011 | |
1012 | /* |
1013 | * Save the values for when (options & SEARCH_KEEP) is used. |
1014 | * (there is no "if ()" around this because gcc wants them initialized) |
1015 | */ |
1016 | old_off = spats[0].off; |
1017 | |
1018 | pos = curwin->w_cursor; /* start searching at the cursor position */ |
1019 | |
1020 | /* |
1021 | * Find out the direction of the search. |
1022 | */ |
1023 | if (dirc == 0) |
1024 | dirc = spats[0].off.dir; |
1025 | else { |
1026 | spats[0].off.dir = dirc; |
1027 | set_vv_searchforward(); |
1028 | } |
1029 | if (options & SEARCH_REV) { |
1030 | if (dirc == '/') |
1031 | dirc = '?'; |
1032 | else |
1033 | dirc = '/'; |
1034 | } |
1035 | |
1036 | /* If the cursor is in a closed fold, don't find another match in the same |
1037 | * fold. */ |
1038 | if (dirc == '/') { |
1039 | if (hasFolding(pos.lnum, NULL, &pos.lnum)) |
1040 | pos.col = MAXCOL - 2; /* avoid overflow when adding 1 */ |
1041 | } else { |
1042 | if (hasFolding(pos.lnum, &pos.lnum, NULL)) |
1043 | pos.col = 0; |
1044 | } |
1045 | |
1046 | /* |
1047 | * Turn 'hlsearch' highlighting back on. |
1048 | */ |
1049 | if (no_hlsearch && !(options & SEARCH_KEEP)) { |
1050 | redraw_all_later(SOME_VALID); |
1051 | set_no_hlsearch(false); |
1052 | } |
1053 | |
1054 | /* |
1055 | * Repeat the search when pattern followed by ';', e.g. "/foo/;?bar". |
1056 | */ |
1057 | for (;; ) { |
1058 | bool show_top_bot_msg = false; |
1059 | |
1060 | searchstr = pat; |
1061 | dircp = NULL; |
1062 | /* use previous pattern */ |
1063 | if (pat == NULL || *pat == NUL || *pat == dirc) { |
1064 | if (spats[RE_SEARCH].pat == NULL) { // no previous pattern |
1065 | searchstr = spats[RE_SUBST].pat; |
1066 | if (searchstr == NULL) { |
1067 | EMSG(_(e_noprevre)); |
1068 | retval = 0; |
1069 | goto end_do_search; |
1070 | } |
1071 | } else { |
1072 | /* make search_regcomp() use spats[RE_SEARCH].pat */ |
1073 | searchstr = (char_u *)"" ; |
1074 | } |
1075 | } |
1076 | |
1077 | if (pat != NULL && *pat != NUL) { /* look for (new) offset */ |
1078 | /* |
1079 | * Find end of regular expression. |
1080 | * If there is a matching '/' or '?', toss it. |
1081 | */ |
1082 | ps = strcopy; |
1083 | p = skip_regexp(pat, dirc, p_magic, &strcopy); |
1084 | if (strcopy != ps) { |
1085 | /* made a copy of "pat" to change "\?" to "?" */ |
1086 | searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy)); |
1087 | pat = strcopy; |
1088 | searchstr = strcopy; |
1089 | } |
1090 | if (*p == dirc) { |
1091 | dircp = p; /* remember where we put the NUL */ |
1092 | *p++ = NUL; |
1093 | } |
1094 | spats[0].off.line = FALSE; |
1095 | spats[0].off.end = FALSE; |
1096 | spats[0].off.off = 0; |
1097 | // Check for a line offset or a character offset. |
1098 | // For get_address (echo off) we don't check for a character |
1099 | // offset, because it is meaningless and the 's' could be a |
1100 | // substitute command. |
1101 | if (*p == '+' || *p == '-' || ascii_isdigit(*p)) { |
1102 | spats[0].off.line = true; |
1103 | } else if ((options & SEARCH_OPT) |
1104 | && (*p == 'e' || *p == 's' || *p == 'b')) { |
1105 | if (*p == 'e') { // end |
1106 | spats[0].off.end = true; |
1107 | } |
1108 | p++; |
1109 | } |
1110 | if (ascii_isdigit(*p) || *p == '+' || *p == '-') { /* got an offset */ |
1111 | /* 'nr' or '+nr' or '-nr' */ |
1112 | if (ascii_isdigit(*p) || ascii_isdigit(*(p + 1))) |
1113 | spats[0].off.off = atol((char *)p); |
1114 | else if (*p == '-') /* single '-' */ |
1115 | spats[0].off.off = -1; |
1116 | else /* single '+' */ |
1117 | spats[0].off.off = 1; |
1118 | ++p; |
1119 | while (ascii_isdigit(*p)) /* skip number */ |
1120 | ++p; |
1121 | } |
1122 | |
1123 | /* compute length of search command for get_address() */ |
1124 | searchcmdlen += (int)(p - pat); |
1125 | |
1126 | pat = p; /* put pat after search command */ |
1127 | } |
1128 | |
1129 | if ((options & SEARCH_ECHO) && messaging() |
1130 | && !cmd_silent && msg_silent == 0) { |
1131 | char_u *trunc; |
1132 | char_u off_buf[40]; |
1133 | size_t off_len = 0; |
1134 | |
1135 | // Compute msg_row early. |
1136 | msg_start(); |
1137 | |
1138 | // Get the offset, so we know how long it is. |
1139 | if (spats[0].off.line || spats[0].off.end || spats[0].off.off) { |
1140 | p = off_buf; // -V507 |
1141 | *p++ = dirc; |
1142 | if (spats[0].off.end) { |
1143 | *p++ = 'e'; |
1144 | } else if (!spats[0].off.line) { |
1145 | *p++ = 's'; |
1146 | } |
1147 | if (spats[0].off.off > 0 || spats[0].off.line) { |
1148 | *p++ = '+'; |
1149 | } |
1150 | *p = NUL; |
1151 | if (spats[0].off.off != 0 || spats[0].off.line) { |
1152 | snprintf((char *)p, sizeof(off_buf) - 1 - (p - off_buf), |
1153 | "%" PRId64, spats[0].off.off); |
1154 | } |
1155 | off_len = STRLEN(off_buf); |
1156 | } |
1157 | |
1158 | if (*searchstr == NUL) { |
1159 | p = spats[last_idx].pat; |
1160 | } else { |
1161 | p = searchstr; |
1162 | } |
1163 | |
1164 | if (!shortmess(SHM_SEARCHCOUNT)) { |
1165 | // Reserve enough space for the search pattern + offset + |
1166 | // search stat. Use all the space available, so that the |
1167 | // search state is right aligned. If there is not enough space |
1168 | // msg_strtrunc() will shorten in the middle. |
1169 | if (ui_has(kUIMessages)) { |
1170 | len = 0; // adjusted below |
1171 | } else if (msg_scrolled != 0) { |
1172 | // Use all the columns. |
1173 | len = (Rows - msg_row) * Columns - 1; |
1174 | } else { |
1175 | // Use up to 'showcmd' column. |
1176 | len = (Rows - msg_row - 1) * Columns + sc_col - 1; |
1177 | } |
1178 | if (len < STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3) { |
1179 | len = STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3; |
1180 | } |
1181 | } else { |
1182 | // Reserve enough space for the search pattern + offset. |
1183 | len = STRLEN(p) + off_len + 3; |
1184 | } |
1185 | |
1186 | msgbuf = xmalloc(len); |
1187 | { |
1188 | memset(msgbuf, ' ', len); |
1189 | msgbuf[0] = dirc; |
1190 | msgbuf[len - 1] = NUL; |
1191 | |
1192 | if (utf_iscomposing(utf_ptr2char(p))) { |
1193 | // Use a space to draw the composing char on. |
1194 | msgbuf[1] = ' '; |
1195 | memmove(msgbuf + 2, p, STRLEN(p)); |
1196 | } else { |
1197 | memmove(msgbuf + 1, p, STRLEN(p)); |
1198 | } |
1199 | if (off_len > 0) { |
1200 | memmove(msgbuf + STRLEN(p) + 1, off_buf, off_len); |
1201 | } |
1202 | |
1203 | trunc = msg_strtrunc(msgbuf, true); |
1204 | if (trunc != NULL) { |
1205 | xfree(msgbuf); |
1206 | msgbuf = trunc; |
1207 | } |
1208 | |
1209 | // The search pattern could be shown on the right in rightleft |
1210 | // mode, but the 'ruler' and 'showcmd' area use it too, thus |
1211 | // it would be blanked out again very soon. Show it on the |
1212 | // left, but do reverse the text. |
1213 | if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { |
1214 | char_u *r = reverse_text(trunc != NULL ? trunc : msgbuf); |
1215 | xfree(msgbuf); |
1216 | msgbuf = r; |
1217 | // move reversed text to beginning of buffer |
1218 | while (*r == ' ') { |
1219 | r++; |
1220 | } |
1221 | size_t pat_len = msgbuf + STRLEN(msgbuf) - r; |
1222 | memmove(msgbuf, r, pat_len); |
1223 | // overwrite old text |
1224 | if ((size_t)(r - msgbuf) >= pat_len) { |
1225 | memset(r, ' ', pat_len); |
1226 | } else { |
1227 | memset(msgbuf + pat_len, ' ', r - msgbuf); |
1228 | } |
1229 | } |
1230 | msg_outtrans(msgbuf); |
1231 | msg_clr_eos(); |
1232 | msg_check(); |
1233 | |
1234 | gotocmdline(false); |
1235 | ui_flush(); |
1236 | msg_nowait = true; // don't wait for this message |
1237 | } |
1238 | } |
1239 | |
1240 | /* |
1241 | * If there is a character offset, subtract it from the current |
1242 | * position, so we don't get stuck at "?pat?e+2" or "/pat/s-2". |
1243 | * Skip this if pos.col is near MAXCOL (closed fold). |
1244 | * This is not done for a line offset, because then we would not be vi |
1245 | * compatible. |
1246 | */ |
1247 | if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2) { |
1248 | if (spats[0].off.off > 0) { |
1249 | for (c = spats[0].off.off; c; --c) |
1250 | if (decl(&pos) == -1) |
1251 | break; |
1252 | if (c) { /* at start of buffer */ |
1253 | pos.lnum = 0; /* allow lnum == 0 here */ |
1254 | pos.col = MAXCOL; |
1255 | } |
1256 | } else { |
1257 | for (c = spats[0].off.off; c; ++c) |
1258 | if (incl(&pos) == -1) |
1259 | break; |
1260 | if (c) { /* at end of buffer */ |
1261 | pos.lnum = curbuf->b_ml.ml_line_count + 1; |
1262 | pos.col = 0; |
1263 | } |
1264 | } |
1265 | } |
1266 | |
1267 | c = searchit(curwin, curbuf, &pos, NULL, dirc == '/' ? FORWARD : BACKWARD, |
1268 | searchstr, count, |
1269 | (spats[0].off.end * SEARCH_END |
1270 | + (options |
1271 | & (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS + SEARCH_MSG |
1272 | + SEARCH_START |
1273 | + ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF)))), |
1274 | RE_LAST, (linenr_T)0, tm, timed_out); |
1275 | |
1276 | if (dircp != NULL) { |
1277 | *dircp = dirc; // restore second '/' or '?' for normal_cmd() |
1278 | } |
1279 | |
1280 | if (!shortmess(SHM_SEARCH) |
1281 | && ((dirc == '/' && lt(pos, curwin->w_cursor)) |
1282 | || (dirc == '?' && lt(curwin->w_cursor, pos)))) { |
1283 | show_top_bot_msg = true; |
1284 | } |
1285 | |
1286 | if (c == FAIL) { |
1287 | retval = 0; |
1288 | goto end_do_search; |
1289 | } |
1290 | if (spats[0].off.end && oap != NULL) |
1291 | oap->inclusive = true; /* 'e' includes last character */ |
1292 | |
1293 | retval = 1; /* pattern found */ |
1294 | |
1295 | /* |
1296 | * Add character and/or line offset |
1297 | */ |
1298 | if (!(options & SEARCH_NOOF) || (pat != NULL && *pat == ';')) { |
1299 | pos_T org_pos = pos; |
1300 | |
1301 | if (spats[0].off.line) { // Add the offset to the line number. |
1302 | c = pos.lnum + spats[0].off.off; |
1303 | if (c < 1) |
1304 | pos.lnum = 1; |
1305 | else if (c > curbuf->b_ml.ml_line_count) |
1306 | pos.lnum = curbuf->b_ml.ml_line_count; |
1307 | else |
1308 | pos.lnum = c; |
1309 | pos.col = 0; |
1310 | |
1311 | retval = 2; /* pattern found, line offset added */ |
1312 | } else if (pos.col < MAXCOL - 2) { /* just in case */ |
1313 | /* to the right, check for end of file */ |
1314 | c = spats[0].off.off; |
1315 | if (c > 0) { |
1316 | while (c-- > 0) |
1317 | if (incl(&pos) == -1) |
1318 | break; |
1319 | } |
1320 | /* to the left, check for start of file */ |
1321 | else { |
1322 | while (c++ < 0) |
1323 | if (decl(&pos) == -1) |
1324 | break; |
1325 | } |
1326 | } |
1327 | if (!equalpos(pos, org_pos)) { |
1328 | has_offset = true; |
1329 | } |
1330 | } |
1331 | |
1332 | // Show [1/15] if 'S' is not in 'shortmess'. |
1333 | if ((options & SEARCH_ECHO) |
1334 | && messaging() |
1335 | && !(cmd_silent + msg_silent) |
1336 | && c != FAIL |
1337 | && !shortmess(SHM_SEARCHCOUNT) |
1338 | && msgbuf != NULL) { |
1339 | search_stat(dirc, &pos, show_top_bot_msg, msgbuf, |
1340 | (count != 1 || has_offset)); |
1341 | } |
1342 | |
1343 | // The search command can be followed by a ';' to do another search. |
1344 | // For example: "/pat/;/foo/+3;?bar" |
1345 | // This is like doing another search command, except: |
1346 | // - The remembered direction '/' or '?' is from the first search. |
1347 | // - When an error happens the cursor isn't moved at all. |
1348 | // Don't do this when called by get_address() (it handles ';' itself). |
1349 | if (!(options & SEARCH_OPT) || pat == NULL || *pat != ';') { |
1350 | break; |
1351 | } |
1352 | |
1353 | dirc = *++pat; |
1354 | if (dirc != '?' && dirc != '/') { |
1355 | retval = 0; |
1356 | EMSG(_("E386: Expected '?' or '/' after ';'" )); |
1357 | goto end_do_search; |
1358 | } |
1359 | ++pat; |
1360 | } |
1361 | |
1362 | if (options & SEARCH_MARK) |
1363 | setpcmark(); |
1364 | curwin->w_cursor = pos; |
1365 | curwin->w_set_curswant = TRUE; |
1366 | |
1367 | end_do_search: |
1368 | if ((options & SEARCH_KEEP) || cmdmod.keeppatterns) |
1369 | spats[0].off = old_off; |
1370 | xfree(msgbuf); |
1371 | |
1372 | return retval; |
1373 | } |
1374 | |
1375 | /* |
1376 | * search_for_exact_line(buf, pos, dir, pat) |
1377 | * |
1378 | * Search for a line starting with the given pattern (ignoring leading |
1379 | * white-space), starting from pos and going in direction "dir". "pos" will |
1380 | * contain the position of the match found. Blank lines match only if |
1381 | * ADDING is set. If p_ic is set then the pattern must be in lowercase. |
1382 | * Return OK for success, or FAIL if no line found. |
1383 | */ |
1384 | int search_for_exact_line(buf_T *buf, pos_T *pos, int dir, char_u *pat) |
1385 | { |
1386 | linenr_T start = 0; |
1387 | char_u *ptr; |
1388 | char_u *p; |
1389 | |
1390 | if (buf->b_ml.ml_line_count == 0) |
1391 | return FAIL; |
1392 | for (;; ) { |
1393 | pos->lnum += dir; |
1394 | if (pos->lnum < 1) { |
1395 | if (p_ws) { |
1396 | pos->lnum = buf->b_ml.ml_line_count; |
1397 | if (!shortmess(SHM_SEARCH)) |
1398 | give_warning((char_u *)_(top_bot_msg), true); |
1399 | } else { |
1400 | pos->lnum = 1; |
1401 | break; |
1402 | } |
1403 | } else if (pos->lnum > buf->b_ml.ml_line_count) { |
1404 | if (p_ws) { |
1405 | pos->lnum = 1; |
1406 | if (!shortmess(SHM_SEARCH)) |
1407 | give_warning((char_u *)_(bot_top_msg), true); |
1408 | } else { |
1409 | pos->lnum = 1; |
1410 | break; |
1411 | } |
1412 | } |
1413 | if (pos->lnum == start) |
1414 | break; |
1415 | if (start == 0) |
1416 | start = pos->lnum; |
1417 | ptr = ml_get_buf(buf, pos->lnum, FALSE); |
1418 | p = skipwhite(ptr); |
1419 | pos->col = (colnr_T) (p - ptr); |
1420 | |
1421 | /* when adding lines the matching line may be empty but it is not |
1422 | * ignored because we are interested in the next line -- Acevedo */ |
1423 | if ((compl_cont_status & CONT_ADDING) |
1424 | && !(compl_cont_status & CONT_SOL)) { |
1425 | if (mb_strcmp_ic((bool)p_ic, (const char *)p, (const char *)pat) == 0) { |
1426 | return OK; |
1427 | } |
1428 | } else if (*p != NUL) { // Ignore empty lines. |
1429 | // Expanding lines or words. |
1430 | assert(compl_length >= 0); |
1431 | if ((p_ic ? mb_strnicmp(p, pat, (size_t)compl_length) |
1432 | : STRNCMP(p, pat, compl_length)) == 0) |
1433 | return OK; |
1434 | } |
1435 | } |
1436 | return FAIL; |
1437 | } |
1438 | |
1439 | /* |
1440 | * Character Searches |
1441 | */ |
1442 | |
1443 | /* |
1444 | * Search for a character in a line. If "t_cmd" is FALSE, move to the |
1445 | * position of the character, otherwise move to just before the char. |
1446 | * Do this "cap->count1" times. |
1447 | * Return FAIL or OK. |
1448 | */ |
1449 | int searchc(cmdarg_T *cap, int t_cmd) |
1450 | { |
1451 | int c = cap->nchar; /* char to search for */ |
1452 | int dir = cap->arg; /* TRUE for searching forward */ |
1453 | long count = cap->count1; /* repeat count */ |
1454 | int col; |
1455 | char_u *p; |
1456 | int len; |
1457 | int stop = TRUE; |
1458 | |
1459 | if (c != NUL) { /* normal search: remember args for repeat */ |
1460 | if (!KeyStuffed) { /* don't remember when redoing */ |
1461 | *lastc = c; |
1462 | set_csearch_direction(dir); |
1463 | set_csearch_until(t_cmd); |
1464 | lastc_bytelen = utf_char2bytes(c, lastc_bytes); |
1465 | if (cap->ncharC1 != 0) { |
1466 | lastc_bytelen += utf_char2bytes(cap->ncharC1, |
1467 | lastc_bytes + lastc_bytelen); |
1468 | if (cap->ncharC2 != 0) { |
1469 | lastc_bytelen += utf_char2bytes(cap->ncharC2, |
1470 | lastc_bytes + lastc_bytelen); |
1471 | } |
1472 | } |
1473 | } |
1474 | } else { // repeat previous search |
1475 | if (*lastc == NUL && lastc_bytelen == 1) { |
1476 | return FAIL; |
1477 | } |
1478 | if (dir) { // repeat in opposite direction |
1479 | dir = -lastcdir; |
1480 | } else { |
1481 | dir = lastcdir; |
1482 | } |
1483 | t_cmd = last_t_cmd; |
1484 | c = *lastc; |
1485 | /* For multi-byte re-use last lastc_bytes[] and lastc_bytelen. */ |
1486 | |
1487 | /* Force a move of at least one char, so ";" and "," will move the |
1488 | * cursor, even if the cursor is right in front of char we are looking |
1489 | * at. */ |
1490 | if (vim_strchr(p_cpo, CPO_SCOLON) == NULL && count == 1 && t_cmd) |
1491 | stop = FALSE; |
1492 | } |
1493 | |
1494 | if (dir == BACKWARD) |
1495 | cap->oap->inclusive = false; |
1496 | else |
1497 | cap->oap->inclusive = true; |
1498 | |
1499 | p = get_cursor_line_ptr(); |
1500 | col = curwin->w_cursor.col; |
1501 | len = (int)STRLEN(p); |
1502 | |
1503 | while (count--) { |
1504 | if (has_mbyte) { |
1505 | for (;; ) { |
1506 | if (dir > 0) { |
1507 | col += (*mb_ptr2len)(p + col); |
1508 | if (col >= len) |
1509 | return FAIL; |
1510 | } else { |
1511 | if (col == 0) |
1512 | return FAIL; |
1513 | col -= utf_head_off(p, p + col - 1) + 1; |
1514 | } |
1515 | if (lastc_bytelen == 1) { |
1516 | if (p[col] == c && stop) { |
1517 | break; |
1518 | } |
1519 | } else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) { |
1520 | break; |
1521 | } |
1522 | stop = true; |
1523 | } |
1524 | } else { |
1525 | for (;; ) { |
1526 | if ((col += dir) < 0 || col >= len) |
1527 | return FAIL; |
1528 | if (p[col] == c && stop) |
1529 | break; |
1530 | stop = TRUE; |
1531 | } |
1532 | } |
1533 | } |
1534 | |
1535 | if (t_cmd) { |
1536 | // Backup to before the character (possibly double-byte). |
1537 | col -= dir; |
1538 | if (dir < 0) { |
1539 | // Landed on the search char which is lastc_bytelen long. |
1540 | col += lastc_bytelen - 1; |
1541 | } else { |
1542 | // To previous char, which may be multi-byte. |
1543 | col -= utf_head_off(p, p + col); |
1544 | } |
1545 | } |
1546 | curwin->w_cursor.col = col; |
1547 | |
1548 | return OK; |
1549 | } |
1550 | |
1551 | /* |
1552 | * "Other" Searches |
1553 | */ |
1554 | |
1555 | /* |
1556 | * findmatch - find the matching paren or brace |
1557 | * |
1558 | * Improvement over vi: Braces inside quotes are ignored. |
1559 | */ |
1560 | pos_T *findmatch(oparg_T *oap, int initc) |
1561 | { |
1562 | return findmatchlimit(oap, initc, 0, 0); |
1563 | } |
1564 | |
1565 | // Return true if the character before "linep[col]" equals "ch". |
1566 | // Return false if "col" is zero. |
1567 | // Update "*prevcol" to the column of the previous character, unless "prevcol" |
1568 | // is NULL. |
1569 | // Handles multibyte string correctly. |
1570 | static bool check_prevcol(char_u *linep, int col, int ch, int *prevcol) |
1571 | { |
1572 | col--; |
1573 | if (col > 0) { |
1574 | col -= utf_head_off(linep, linep + col); |
1575 | } |
1576 | if (prevcol) { |
1577 | *prevcol = col; |
1578 | } |
1579 | return (col >= 0 && linep[col] == ch) ? true : false; |
1580 | } |
1581 | |
1582 | /* |
1583 | * Raw string start is found at linep[startpos.col - 1]. |
1584 | * Return true if the matching end can be found between startpos and endpos. |
1585 | */ |
1586 | static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos) |
1587 | { |
1588 | char_u *p; |
1589 | char_u *delim_copy; |
1590 | size_t delim_len; |
1591 | linenr_T lnum; |
1592 | |
1593 | for (p = linep + startpos->col + 1; *p && *p != '('; p++) {} |
1594 | |
1595 | delim_len = (p - linep) - startpos->col - 1; |
1596 | delim_copy = vim_strnsave(linep + startpos->col + 1, delim_len); |
1597 | bool found = false; |
1598 | for (lnum = startpos->lnum; lnum <= endpos->lnum; lnum++) { |
1599 | char_u *line = ml_get(lnum); |
1600 | |
1601 | for (p = line + (lnum == startpos->lnum ? startpos->col + 1 : 0); *p; p++) { |
1602 | if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col) { |
1603 | break; |
1604 | } |
1605 | if (*p == ')' && p[delim_len + 1] == '"' |
1606 | && STRNCMP(delim_copy, p + 1, delim_len) == 0) { |
1607 | found = true; |
1608 | break; |
1609 | } |
1610 | } |
1611 | if (found) { |
1612 | break; |
1613 | } |
1614 | } |
1615 | xfree(delim_copy); |
1616 | return found; |
1617 | } |
1618 | |
1619 | /* |
1620 | * findmatchlimit -- find the matching paren or brace, if it exists within |
1621 | * maxtravel lines of the cursor. A maxtravel of 0 means search until falling |
1622 | * off the edge of the file. |
1623 | * |
1624 | * "initc" is the character to find a match for. NUL means to find the |
1625 | * character at or after the cursor. Special values: |
1626 | * '*' look for C-style comment / * |
1627 | * '/' look for C-style comment / *, ignoring comment-end |
1628 | * '#' look for preprocessor directives |
1629 | * 'R' look for raw string start: R"delim(text)delim" (only backwards) |
1630 | * |
1631 | * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#') |
1632 | * FM_FORWARD search forwards (when initc is '/', '*' or '#') |
1633 | * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0) |
1634 | * FM_SKIPCOMM skip comments (not implemented yet!) |
1635 | * |
1636 | * "oap" is only used to set oap->motion_type for a linewise motion, it can be |
1637 | * NULL |
1638 | */ |
1639 | |
1640 | pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) |
1641 | { |
1642 | static pos_T pos; // current search position |
1643 | int findc = 0; // matching brace |
1644 | int count = 0; // cumulative number of braces |
1645 | int backwards = false; // init for gcc |
1646 | bool raw_string = false; // search for raw string |
1647 | bool inquote = false; // true when inside quotes |
1648 | char_u *ptr; |
1649 | int hash_dir = 0; // Direction searched for # things |
1650 | int = 0; // Direction searched for comments |
1651 | int traveled = 0; // how far we've searched so far |
1652 | bool ignore_cend = false; // ignore comment end |
1653 | int match_escaped = 0; // search for escaped match |
1654 | int dir; // Direction to search |
1655 | int = MAXCOL; // start of / / comment |
1656 | bool lispcomm = false; // inside of Lisp-style comment |
1657 | bool lisp = curbuf->b_p_lisp; // engage Lisp-specific hacks ;) |
1658 | |
1659 | pos = curwin->w_cursor; |
1660 | pos.coladd = 0; |
1661 | char_u *linep = ml_get(pos.lnum); // pointer to current line |
1662 | |
1663 | // vi compatible matching |
1664 | bool cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL); |
1665 | // don't recognize backslashes |
1666 | bool cpo_bsl = (vim_strchr(p_cpo, CPO_MATCHBSL) != NULL); |
1667 | |
1668 | /* Direction to search when initc is '/', '*' or '#' */ |
1669 | if (flags & FM_BACKWARD) |
1670 | dir = BACKWARD; |
1671 | else if (flags & FM_FORWARD) |
1672 | dir = FORWARD; |
1673 | else |
1674 | dir = 0; |
1675 | |
1676 | /* |
1677 | * if initc given, look in the table for the matching character |
1678 | * '/' and '*' are special cases: look for start or end of comment. |
1679 | * When '/' is used, we ignore running backwards into a star-slash, for |
1680 | * "[*" command, we just want to find any comment. |
1681 | */ |
1682 | if (initc == '/' || initc == '*' || initc == 'R') { |
1683 | comment_dir = dir; |
1684 | if (initc == '/') |
1685 | ignore_cend = true; |
1686 | backwards = (dir == FORWARD) ? false : true; |
1687 | raw_string = (initc == 'R'); |
1688 | initc = NUL; |
1689 | } else if (initc != '#' && initc != NUL) { |
1690 | find_mps_values(&initc, &findc, &backwards, TRUE); |
1691 | if (findc == NUL) |
1692 | return NULL; |
1693 | } else { |
1694 | /* |
1695 | * Either initc is '#', or no initc was given and we need to look |
1696 | * under the cursor. |
1697 | */ |
1698 | if (initc == '#') { |
1699 | hash_dir = dir; |
1700 | } else { |
1701 | /* |
1702 | * initc was not given, must look for something to match under |
1703 | * or near the cursor. |
1704 | * Only check for special things when 'cpo' doesn't have '%'. |
1705 | */ |
1706 | if (!cpo_match) { |
1707 | /* Are we before or at #if, #else etc.? */ |
1708 | ptr = skipwhite(linep); |
1709 | if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep)) { |
1710 | ptr = skipwhite(ptr + 1); |
1711 | if ( STRNCMP(ptr, "if" , 2) == 0 |
1712 | || STRNCMP(ptr, "endif" , 5) == 0 |
1713 | || STRNCMP(ptr, "el" , 2) == 0) |
1714 | hash_dir = 1; |
1715 | } |
1716 | /* Are we on a comment? */ |
1717 | else if (linep[pos.col] == '/') { |
1718 | if (linep[pos.col + 1] == '*') { |
1719 | comment_dir = FORWARD; |
1720 | backwards = FALSE; |
1721 | pos.col++; |
1722 | } else if (pos.col > 0 && linep[pos.col - 1] == '*') { |
1723 | comment_dir = BACKWARD; |
1724 | backwards = TRUE; |
1725 | pos.col--; |
1726 | } |
1727 | } else if (linep[pos.col] == '*') { |
1728 | if (linep[pos.col + 1] == '/') { |
1729 | comment_dir = BACKWARD; |
1730 | backwards = TRUE; |
1731 | } else if (pos.col > 0 && linep[pos.col - 1] == '/') { |
1732 | comment_dir = FORWARD; |
1733 | backwards = FALSE; |
1734 | } |
1735 | } |
1736 | } |
1737 | |
1738 | /* |
1739 | * If we are not on a comment or the # at the start of a line, then |
1740 | * look for brace anywhere on this line after the cursor. |
1741 | */ |
1742 | if (!hash_dir && !comment_dir) { |
1743 | /* |
1744 | * Find the brace under or after the cursor. |
1745 | * If beyond the end of the line, use the last character in |
1746 | * the line. |
1747 | */ |
1748 | if (linep[pos.col] == NUL && pos.col) |
1749 | --pos.col; |
1750 | for (;; ) { |
1751 | initc = PTR2CHAR(linep + pos.col); |
1752 | if (initc == NUL) |
1753 | break; |
1754 | |
1755 | find_mps_values(&initc, &findc, &backwards, FALSE); |
1756 | if (findc) |
1757 | break; |
1758 | pos.col += MB_PTR2LEN(linep + pos.col); |
1759 | } |
1760 | if (!findc) { |
1761 | /* no brace in the line, maybe use " #if" then */ |
1762 | if (!cpo_match && *skipwhite(linep) == '#') |
1763 | hash_dir = 1; |
1764 | else |
1765 | return NULL; |
1766 | } else if (!cpo_bsl) { |
1767 | int col, bslcnt = 0; |
1768 | |
1769 | /* Set "match_escaped" if there are an odd number of |
1770 | * backslashes. */ |
1771 | for (col = pos.col; check_prevcol(linep, col, '\\', &col); ) |
1772 | bslcnt++; |
1773 | match_escaped = (bslcnt & 1); |
1774 | } |
1775 | } |
1776 | } |
1777 | if (hash_dir) { |
1778 | /* |
1779 | * Look for matching #if, #else, #elif, or #endif |
1780 | */ |
1781 | if (oap != NULL) { |
1782 | oap->motion_type = kMTLineWise; // Linewise for this case only |
1783 | } |
1784 | if (initc != '#') { |
1785 | ptr = skipwhite(skipwhite(linep) + 1); |
1786 | if (STRNCMP(ptr, "if" , 2) == 0 || STRNCMP(ptr, "el" , 2) == 0) |
1787 | hash_dir = 1; |
1788 | else if (STRNCMP(ptr, "endif" , 5) == 0) |
1789 | hash_dir = -1; |
1790 | else |
1791 | return NULL; |
1792 | } |
1793 | pos.col = 0; |
1794 | while (!got_int) { |
1795 | if (hash_dir > 0) { |
1796 | if (pos.lnum == curbuf->b_ml.ml_line_count) |
1797 | break; |
1798 | } else if (pos.lnum == 1) |
1799 | break; |
1800 | pos.lnum += hash_dir; |
1801 | linep = ml_get(pos.lnum); |
1802 | line_breakcheck(); /* check for CTRL-C typed */ |
1803 | ptr = skipwhite(linep); |
1804 | if (*ptr != '#') |
1805 | continue; |
1806 | pos.col = (colnr_T) (ptr - linep); |
1807 | ptr = skipwhite(ptr + 1); |
1808 | if (hash_dir > 0) { |
1809 | if (STRNCMP(ptr, "if" , 2) == 0) |
1810 | count++; |
1811 | else if (STRNCMP(ptr, "el" , 2) == 0) { |
1812 | if (count == 0) |
1813 | return &pos; |
1814 | } else if (STRNCMP(ptr, "endif" , 5) == 0) { |
1815 | if (count == 0) |
1816 | return &pos; |
1817 | count--; |
1818 | } |
1819 | } else { |
1820 | if (STRNCMP(ptr, "if" , 2) == 0) { |
1821 | if (count == 0) |
1822 | return &pos; |
1823 | count--; |
1824 | } else if (initc == '#' && STRNCMP(ptr, "el" , 2) == 0) { |
1825 | if (count == 0) |
1826 | return &pos; |
1827 | } else if (STRNCMP(ptr, "endif" , 5) == 0) |
1828 | count++; |
1829 | } |
1830 | } |
1831 | return NULL; |
1832 | } |
1833 | } |
1834 | |
1835 | // This is just guessing: when 'rightleft' is set, search for a matching |
1836 | // paren/brace in the other direction. |
1837 | if (curwin->w_p_rl && vim_strchr((char_u *)"()[]{}<>" , initc) != NULL) { |
1838 | backwards = !backwards; |
1839 | } |
1840 | |
1841 | int do_quotes = -1; // check for quotes in current line |
1842 | int at_start; // do_quotes value at start position |
1843 | TriState start_in_quotes = kNone; // start position is in quotes |
1844 | pos_T match_pos; // Where last slash-star was found |
1845 | clearpos(&match_pos); |
1846 | |
1847 | /* backward search: Check if this line contains a single-line comment */ |
1848 | if ((backwards && comment_dir) |
1849 | || lisp |
1850 | ) |
1851 | comment_col = check_linecomment(linep); |
1852 | if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col) { |
1853 | lispcomm = true; // find match inside this comment |
1854 | } |
1855 | while (!got_int) { |
1856 | /* |
1857 | * Go to the next position, forward or backward. We could use |
1858 | * inc() and dec() here, but that is much slower |
1859 | */ |
1860 | if (backwards) { |
1861 | /* char to match is inside of comment, don't search outside */ |
1862 | if (lispcomm && pos.col < (colnr_T)comment_col) |
1863 | break; |
1864 | if (pos.col == 0) { /* at start of line, go to prev. one */ |
1865 | if (pos.lnum == 1) /* start of file */ |
1866 | break; |
1867 | --pos.lnum; |
1868 | |
1869 | if (maxtravel > 0 && ++traveled > maxtravel) |
1870 | break; |
1871 | |
1872 | linep = ml_get(pos.lnum); |
1873 | pos.col = (colnr_T)STRLEN(linep); /* pos.col on trailing NUL */ |
1874 | do_quotes = -1; |
1875 | line_breakcheck(); |
1876 | |
1877 | /* Check if this line contains a single-line comment */ |
1878 | if (comment_dir |
1879 | || lisp |
1880 | ) |
1881 | comment_col = check_linecomment(linep); |
1882 | /* skip comment */ |
1883 | if (lisp && comment_col != MAXCOL) |
1884 | pos.col = comment_col; |
1885 | } else { |
1886 | pos.col--; |
1887 | pos.col -= utf_head_off(linep, linep + pos.col); |
1888 | } |
1889 | } else { /* forward search */ |
1890 | if (linep[pos.col] == NUL |
1891 | /* at end of line, go to next one */ |
1892 | /* don't search for match in comment */ |
1893 | || (lisp && comment_col != MAXCOL |
1894 | && pos.col == (colnr_T)comment_col) |
1895 | ) { |
1896 | if (pos.lnum == curbuf->b_ml.ml_line_count /* end of file */ |
1897 | /* line is exhausted and comment with it, |
1898 | * don't search for match in code */ |
1899 | || lispcomm |
1900 | ) |
1901 | break; |
1902 | ++pos.lnum; |
1903 | |
1904 | if (maxtravel && traveled++ > maxtravel) |
1905 | break; |
1906 | |
1907 | linep = ml_get(pos.lnum); |
1908 | pos.col = 0; |
1909 | do_quotes = -1; |
1910 | line_breakcheck(); |
1911 | if (lisp) /* find comment pos in new line */ |
1912 | comment_col = check_linecomment(linep); |
1913 | } else { |
1914 | if (has_mbyte) |
1915 | pos.col += (*mb_ptr2len)(linep + pos.col); |
1916 | else |
1917 | ++pos.col; |
1918 | } |
1919 | } |
1920 | |
1921 | // If FM_BLOCKSTOP given, stop at a '{' or '}' in column 0. |
1922 | if (pos.col == 0 && (flags & FM_BLOCKSTOP) |
1923 | && (linep[0] == '{' || linep[0] == '}')) { |
1924 | if (linep[0] == findc && count == 0) { // match! |
1925 | return &pos; |
1926 | } |
1927 | break; // out of scope |
1928 | } |
1929 | |
1930 | if (comment_dir) { |
1931 | /* Note: comments do not nest, and we ignore quotes in them */ |
1932 | /* TODO: ignore comment brackets inside strings */ |
1933 | if (comment_dir == FORWARD) { |
1934 | if (linep[pos.col] == '*' && linep[pos.col + 1] == '/') { |
1935 | pos.col++; |
1936 | return &pos; |
1937 | } |
1938 | } else { /* Searching backwards */ |
1939 | /* |
1940 | * A comment may contain / * or / /, it may also start or end |
1941 | * with / * /. Ignore a / * after / / and after *. |
1942 | */ |
1943 | if (pos.col == 0) |
1944 | continue; |
1945 | else if (raw_string) |
1946 | { |
1947 | if (linep[pos.col - 1] == 'R' |
1948 | && linep[pos.col] == '"' |
1949 | && vim_strchr(linep + pos.col + 1, '(') != NULL) |
1950 | { |
1951 | /* Possible start of raw string. Now that we have the |
1952 | * delimiter we can check if it ends before where we |
1953 | * started searching, or before the previously found |
1954 | * raw string start. */ |
1955 | if (!find_rawstring_end(linep, &pos, |
1956 | count > 0 ? &match_pos : &curwin->w_cursor)) |
1957 | { |
1958 | count++; |
1959 | match_pos = pos; |
1960 | match_pos.col--; |
1961 | } |
1962 | linep = ml_get(pos.lnum); /* may have been released */ |
1963 | } |
1964 | } else if ( linep[pos.col - 1] == '/' |
1965 | && linep[pos.col] == '*' |
1966 | && (pos.col == 1 || linep[pos.col - 2] != '*') |
1967 | && (int)pos.col < comment_col) { |
1968 | count++; |
1969 | match_pos = pos; |
1970 | match_pos.col--; |
1971 | } else if (linep[pos.col - 1] == '*' && linep[pos.col] == '/') { |
1972 | if (count > 0) |
1973 | pos = match_pos; |
1974 | else if (pos.col > 1 && linep[pos.col - 2] == '/' |
1975 | && (int)pos.col <= comment_col) |
1976 | pos.col -= 2; |
1977 | else if (ignore_cend) |
1978 | continue; |
1979 | else |
1980 | return NULL; |
1981 | return &pos; |
1982 | } |
1983 | } |
1984 | continue; |
1985 | } |
1986 | |
1987 | /* |
1988 | * If smart matching ('cpoptions' does not contain '%'), braces inside |
1989 | * of quotes are ignored, but only if there is an even number of |
1990 | * quotes in the line. |
1991 | */ |
1992 | if (cpo_match) |
1993 | do_quotes = 0; |
1994 | else if (do_quotes == -1) { |
1995 | /* |
1996 | * Count the number of quotes in the line, skipping \" and '"'. |
1997 | * Watch out for "\\". |
1998 | */ |
1999 | at_start = do_quotes; |
2000 | for (ptr = linep; *ptr; ++ptr) { |
2001 | if (ptr == linep + pos.col + backwards) |
2002 | at_start = (do_quotes & 1); |
2003 | if (*ptr == '"' |
2004 | && (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\'')) |
2005 | ++do_quotes; |
2006 | if (*ptr == '\\' && ptr[1] != NUL) |
2007 | ++ptr; |
2008 | } |
2009 | do_quotes &= 1; /* result is 1 with even number of quotes */ |
2010 | |
2011 | /* |
2012 | * If we find an uneven count, check current line and previous |
2013 | * one for a '\' at the end. |
2014 | */ |
2015 | if (!do_quotes) { |
2016 | inquote = false; |
2017 | if (ptr[-1] == '\\') { |
2018 | do_quotes = 1; |
2019 | if (start_in_quotes == kNone) { |
2020 | // Do we need to use at_start here? |
2021 | inquote = true; |
2022 | start_in_quotes = kTrue; |
2023 | } else if (backwards) { |
2024 | inquote = true; |
2025 | } |
2026 | } |
2027 | if (pos.lnum > 1) { |
2028 | ptr = ml_get(pos.lnum - 1); |
2029 | if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\') { |
2030 | do_quotes = 1; |
2031 | if (start_in_quotes == kNone) { |
2032 | inquote = at_start; |
2033 | if (inquote) { |
2034 | start_in_quotes = kTrue; |
2035 | } |
2036 | } else if (!backwards) { |
2037 | inquote = true; |
2038 | } |
2039 | } |
2040 | |
2041 | /* ml_get() only keeps one line, need to get linep again */ |
2042 | linep = ml_get(pos.lnum); |
2043 | } |
2044 | } |
2045 | } |
2046 | if (start_in_quotes == kNone) { |
2047 | start_in_quotes = kFalse; |
2048 | } |
2049 | |
2050 | /* |
2051 | * If 'smartmatch' is set: |
2052 | * Things inside quotes are ignored by setting 'inquote'. If we |
2053 | * find a quote without a preceding '\' invert 'inquote'. At the |
2054 | * end of a line not ending in '\' we reset 'inquote'. |
2055 | * |
2056 | * In lines with an uneven number of quotes (without preceding '\') |
2057 | * we do not know which part to ignore. Therefore we only set |
2058 | * inquote if the number of quotes in a line is even, unless this |
2059 | * line or the previous one ends in a '\'. Complicated, isn't it? |
2060 | */ |
2061 | const int c = PTR2CHAR(linep + pos.col); |
2062 | switch (c) { |
2063 | case NUL: |
2064 | /* at end of line without trailing backslash, reset inquote */ |
2065 | if (pos.col == 0 || linep[pos.col - 1] != '\\') { |
2066 | inquote = false; |
2067 | start_in_quotes = kFalse; |
2068 | } |
2069 | break; |
2070 | |
2071 | case '"': |
2072 | /* a quote that is preceded with an odd number of backslashes is |
2073 | * ignored */ |
2074 | if (do_quotes) { |
2075 | int col; |
2076 | |
2077 | for (col = pos.col - 1; col >= 0; --col) |
2078 | if (linep[col] != '\\') |
2079 | break; |
2080 | if ((((int)pos.col - 1 - col) & 1) == 0) { |
2081 | inquote = !inquote; |
2082 | start_in_quotes = kFalse; |
2083 | } |
2084 | } |
2085 | break; |
2086 | |
2087 | /* |
2088 | * If smart matching ('cpoptions' does not contain '%'): |
2089 | * Skip things in single quotes: 'x' or '\x'. Be careful for single |
2090 | * single quotes, eg jon's. Things like '\233' or '\x3f' are not |
2091 | * skipped, there is never a brace in them. |
2092 | * Ignore this when finding matches for `'. |
2093 | */ |
2094 | case '\'': |
2095 | if (!cpo_match && initc != '\'' && findc != '\'') { |
2096 | if (backwards) { |
2097 | if (pos.col > 1) { |
2098 | if (linep[pos.col - 2] == '\'') { |
2099 | pos.col -= 2; |
2100 | break; |
2101 | } else if (linep[pos.col - 2] == '\\' |
2102 | && pos.col > 2 && linep[pos.col - 3] == '\'') { |
2103 | pos.col -= 3; |
2104 | break; |
2105 | } |
2106 | } |
2107 | } else if (linep[pos.col + 1]) { // forward search |
2108 | if (linep[pos.col + 1] == '\\' |
2109 | && linep[pos.col + 2] && linep[pos.col + 3] == '\'') { |
2110 | pos.col += 3; |
2111 | break; |
2112 | } else if (linep[pos.col + 2] == '\'') { |
2113 | pos.col += 2; |
2114 | break; |
2115 | } |
2116 | } |
2117 | } |
2118 | FALLTHROUGH; |
2119 | |
2120 | default: |
2121 | /* |
2122 | * For Lisp skip over backslashed (), {} and []. |
2123 | * (actually, we skip #\( et al) |
2124 | */ |
2125 | if (curbuf->b_p_lisp |
2126 | && vim_strchr((char_u *)"(){}[]" , c) != NULL |
2127 | && pos.col > 1 |
2128 | && check_prevcol(linep, pos.col, '\\', NULL) |
2129 | && check_prevcol(linep, pos.col - 1, '#', NULL)) |
2130 | break; |
2131 | |
2132 | /* Check for match outside of quotes, and inside of |
2133 | * quotes when the start is also inside of quotes. */ |
2134 | if ((!inquote || start_in_quotes == kTrue) |
2135 | && (c == initc || c == findc)) { |
2136 | int col, bslcnt = 0; |
2137 | |
2138 | if (!cpo_bsl) { |
2139 | for (col = pos.col; check_prevcol(linep, col, '\\', &col); ) |
2140 | bslcnt++; |
2141 | } |
2142 | /* Only accept a match when 'M' is in 'cpo' or when escaping |
2143 | * is what we expect. */ |
2144 | if (cpo_bsl || (bslcnt & 1) == match_escaped) { |
2145 | if (c == initc) |
2146 | count++; |
2147 | else { |
2148 | if (count == 0) |
2149 | return &pos; |
2150 | count--; |
2151 | } |
2152 | } |
2153 | } |
2154 | } |
2155 | } |
2156 | |
2157 | if (comment_dir == BACKWARD && count > 0) { |
2158 | pos = match_pos; |
2159 | return &pos; |
2160 | } |
2161 | return (pos_T *)NULL; /* never found it */ |
2162 | } |
2163 | |
2164 | /* |
2165 | * Check if line[] contains a / / comment. |
2166 | * Return MAXCOL if not, otherwise return the column. |
2167 | * TODO: skip strings. |
2168 | */ |
2169 | static int (char_u *line) |
2170 | { |
2171 | char_u *p; |
2172 | |
2173 | p = line; |
2174 | /* skip Lispish one-line comments */ |
2175 | if (curbuf->b_p_lisp) { |
2176 | if (vim_strchr(p, ';') != NULL) { /* there may be comments */ |
2177 | int in_str = FALSE; /* inside of string */ |
2178 | |
2179 | p = line; /* scan from start */ |
2180 | while ((p = vim_strpbrk(p, (char_u *)"\";" )) != NULL) { |
2181 | if (*p == '"') { |
2182 | if (in_str) { |
2183 | if (*(p - 1) != '\\') /* skip escaped quote */ |
2184 | in_str = FALSE; |
2185 | } else if (p == line || ((p - line) >= 2 |
2186 | /* skip #\" form */ |
2187 | && *(p - 1) != '\\' && *(p - 2) != '#')) |
2188 | in_str = TRUE; |
2189 | } else if (!in_str && ((p - line) < 2 |
2190 | || (*(p - 1) != '\\' && *(p - 2) != '#'))) |
2191 | break; /* found! */ |
2192 | ++p; |
2193 | } |
2194 | } else |
2195 | p = NULL; |
2196 | } else |
2197 | while ((p = vim_strchr(p, '/')) != NULL) { |
2198 | /* accept a double /, unless it's preceded with * and followed by *, |
2199 | * because * / / * is an end and start of a C comment */ |
2200 | if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')) |
2201 | break; |
2202 | ++p; |
2203 | } |
2204 | |
2205 | if (p == NULL) |
2206 | return MAXCOL; |
2207 | return (int)(p - line); |
2208 | } |
2209 | |
2210 | /* |
2211 | * Move cursor briefly to character matching the one under the cursor. |
2212 | * Used for Insert mode and "r" command. |
2213 | * Show the match only if it is visible on the screen. |
2214 | * If there isn't a match, then beep. |
2215 | */ |
2216 | void |
2217 | showmatch( |
2218 | int c // char to show match for |
2219 | ) |
2220 | { |
2221 | pos_T *lpos, save_cursor; |
2222 | pos_T mpos; |
2223 | colnr_T vcol; |
2224 | long save_so; |
2225 | long save_siso; |
2226 | int save_state; |
2227 | colnr_T save_dollar_vcol; |
2228 | char_u *p; |
2229 | |
2230 | /* |
2231 | * Only show match for chars in the 'matchpairs' option. |
2232 | */ |
2233 | /* 'matchpairs' is "x:y,x:y" */ |
2234 | for (p = curbuf->b_p_mps; *p != NUL; ++p) { |
2235 | if (PTR2CHAR(p) == c && (curwin->w_p_rl ^ p_ri)) |
2236 | break; |
2237 | p += MB_PTR2LEN(p) + 1; |
2238 | if (PTR2CHAR(p) == c |
2239 | && !(curwin->w_p_rl ^ p_ri) |
2240 | ) |
2241 | break; |
2242 | p += MB_PTR2LEN(p); |
2243 | if (*p == NUL) |
2244 | return; |
2245 | } |
2246 | |
2247 | if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep |
2248 | vim_beep(BO_MATCH); |
2249 | } else if (lpos->lnum >= curwin->w_topline |
2250 | && lpos->lnum < curwin->w_botline) { |
2251 | if (!curwin->w_p_wrap) { |
2252 | getvcol(curwin, lpos, NULL, &vcol, NULL); |
2253 | } |
2254 | if (curwin->w_p_wrap |
2255 | || (vcol >= curwin->w_leftcol |
2256 | && vcol < curwin->w_leftcol + curwin->w_width_inner)) { |
2257 | mpos = *lpos; // save the pos, update_screen() may change it |
2258 | save_cursor = curwin->w_cursor; |
2259 | save_so = p_so; |
2260 | save_siso = p_siso; |
2261 | /* Handle "$" in 'cpo': If the ')' is typed on top of the "$", |
2262 | * stop displaying the "$". */ |
2263 | if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) |
2264 | dollar_vcol = -1; |
2265 | ++curwin->w_virtcol; /* do display ')' just before "$" */ |
2266 | update_screen(VALID); /* show the new char first */ |
2267 | |
2268 | save_dollar_vcol = dollar_vcol; |
2269 | save_state = State; |
2270 | State = SHOWMATCH; |
2271 | ui_cursor_shape(); /* may show different cursor shape */ |
2272 | curwin->w_cursor = mpos; /* move to matching char */ |
2273 | p_so = 0; /* don't use 'scrolloff' here */ |
2274 | p_siso = 0; /* don't use 'sidescrolloff' here */ |
2275 | showruler(FALSE); |
2276 | setcursor(); |
2277 | ui_flush(); |
2278 | /* Restore dollar_vcol(), because setcursor() may call curs_rows() |
2279 | * which resets it if the matching position is in a previous line |
2280 | * and has a higher column number. */ |
2281 | dollar_vcol = save_dollar_vcol; |
2282 | |
2283 | /* |
2284 | * brief pause, unless 'm' is present in 'cpo' and a character is |
2285 | * available. |
2286 | */ |
2287 | if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) |
2288 | os_delay(p_mat * 100L, true); |
2289 | else if (!char_avail()) |
2290 | os_delay(p_mat * 100L, false); |
2291 | curwin->w_cursor = save_cursor; /* restore cursor position */ |
2292 | p_so = save_so; |
2293 | p_siso = save_siso; |
2294 | State = save_state; |
2295 | ui_cursor_shape(); /* may show different cursor shape */ |
2296 | } |
2297 | } |
2298 | } |
2299 | |
2300 | // Find the start of the next sentence, searching in the direction specified |
2301 | // by the "dir" argument. The cursor is positioned on the start of the next |
2302 | // sentence when found. If the next sentence is found, return OK. Return FAIL |
2303 | // otherwise. See ":h sentence" for the precise definition of a "sentence" |
2304 | // text object. |
2305 | int findsent(Direction dir, long count) |
2306 | { |
2307 | pos_T pos, tpos; |
2308 | int c; |
2309 | int (*func)(pos_T *); |
2310 | bool noskip = false; // do not skip blanks |
2311 | |
2312 | pos = curwin->w_cursor; |
2313 | if (dir == FORWARD) |
2314 | func = incl; |
2315 | else |
2316 | func = decl; |
2317 | |
2318 | while (count--) { |
2319 | /* |
2320 | * if on an empty line, skip up to a non-empty line |
2321 | */ |
2322 | if (gchar_pos(&pos) == NUL) { |
2323 | do { |
2324 | if ((*func)(&pos) == -1) { |
2325 | break; |
2326 | } |
2327 | } while (gchar_pos(&pos) == NUL); |
2328 | if (dir == FORWARD) { |
2329 | goto found; |
2330 | } |
2331 | // if on the start of a paragraph or a section and searching forward, |
2332 | // go to the next line |
2333 | } else if (dir == FORWARD && pos.col == 0 |
2334 | && startPS(pos.lnum, NUL, false)) { |
2335 | if (pos.lnum == curbuf->b_ml.ml_line_count) { |
2336 | return FAIL; |
2337 | } |
2338 | pos.lnum++; |
2339 | goto found; |
2340 | } else if (dir == BACKWARD) { |
2341 | decl(&pos); |
2342 | } |
2343 | |
2344 | // go back to the previous non-white non-punctuation character |
2345 | bool found_dot = false; |
2346 | while (c = gchar_pos(&pos), ascii_iswhite(c) |
2347 | || vim_strchr((char_u *)".!?)]\"'" , c) != NULL) { |
2348 | tpos = pos; |
2349 | if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) { |
2350 | break; |
2351 | } |
2352 | if (found_dot) { |
2353 | break; |
2354 | } |
2355 | if (vim_strchr((char_u *) ".!?" , c) != NULL) { |
2356 | found_dot = true; |
2357 | } |
2358 | if (vim_strchr((char_u *) ")]\"'" , c) != NULL |
2359 | && vim_strchr((char_u *) ".!?)]\"'" , gchar_pos(&tpos)) == NULL) { |
2360 | break; |
2361 | } |
2362 | decl(&pos); |
2363 | } |
2364 | |
2365 | // remember the line where the search started |
2366 | const int startlnum = pos.lnum; |
2367 | const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; |
2368 | |
2369 | for (;; ) { /* find end of sentence */ |
2370 | c = gchar_pos(&pos); |
2371 | if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE))) { |
2372 | if (dir == BACKWARD && pos.lnum != startlnum) |
2373 | ++pos.lnum; |
2374 | break; |
2375 | } |
2376 | if (c == '.' || c == '!' || c == '?') { |
2377 | tpos = pos; |
2378 | do |
2379 | if ((c = inc(&tpos)) == -1) |
2380 | break; |
2381 | while (vim_strchr((char_u *)")]\"'" , c = gchar_pos(&tpos)) |
2382 | != NULL); |
2383 | if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL |
2384 | || (cpo_J && (c == ' ' && inc(&tpos) >= 0 |
2385 | && gchar_pos(&tpos) == ' '))) { |
2386 | pos = tpos; |
2387 | if (gchar_pos(&pos) == NUL) /* skip NUL at EOL */ |
2388 | inc(&pos); |
2389 | break; |
2390 | } |
2391 | } |
2392 | if ((*func)(&pos) == -1) { |
2393 | if (count) |
2394 | return FAIL; |
2395 | noskip = true; |
2396 | break; |
2397 | } |
2398 | } |
2399 | found: |
2400 | /* skip white space */ |
2401 | while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) |
2402 | if (incl(&pos) == -1) |
2403 | break; |
2404 | } |
2405 | |
2406 | setpcmark(); |
2407 | curwin->w_cursor = pos; |
2408 | return OK; |
2409 | } |
2410 | |
2411 | /* |
2412 | * Find the next paragraph or section in direction 'dir'. |
2413 | * Paragraphs are currently supposed to be separated by empty lines. |
2414 | * If 'what' is NUL we go to the next paragraph. |
2415 | * If 'what' is '{' or '}' we go to the next section. |
2416 | * If 'both' is TRUE also stop at '}'. |
2417 | * Return TRUE if the next paragraph or section was found. |
2418 | */ |
2419 | bool |
2420 | findpar ( |
2421 | bool *pincl, /* Return: true if last char is to be included */ |
2422 | int dir, |
2423 | long count, |
2424 | int what, |
2425 | int both |
2426 | ) |
2427 | { |
2428 | linenr_T curr; |
2429 | bool did_skip; /* true after separating lines have been skipped */ |
2430 | bool first; /* true on first line */ |
2431 | linenr_T fold_first; /* first line of a closed fold */ |
2432 | linenr_T fold_last; /* last line of a closed fold */ |
2433 | bool fold_skipped; /* true if a closed fold was skipped this |
2434 | iteration */ |
2435 | |
2436 | curr = curwin->w_cursor.lnum; |
2437 | |
2438 | while (count--) { |
2439 | did_skip = false; |
2440 | for (first = true;; first = false) { |
2441 | if (*ml_get(curr) != NUL) |
2442 | did_skip = true; |
2443 | |
2444 | /* skip folded lines */ |
2445 | fold_skipped = false; |
2446 | if (first && hasFolding(curr, &fold_first, &fold_last)) { |
2447 | curr = ((dir > 0) ? fold_last : fold_first) + dir; |
2448 | fold_skipped = true; |
2449 | } |
2450 | |
2451 | if (!first && did_skip && startPS(curr, what, both)) |
2452 | break; |
2453 | |
2454 | if (fold_skipped) |
2455 | curr -= dir; |
2456 | if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) { |
2457 | if (count) |
2458 | return false; |
2459 | curr -= dir; |
2460 | break; |
2461 | } |
2462 | } |
2463 | } |
2464 | setpcmark(); |
2465 | if (both && *ml_get(curr) == '}') /* include line with '}' */ |
2466 | ++curr; |
2467 | curwin->w_cursor.lnum = curr; |
2468 | if (curr == curbuf->b_ml.ml_line_count && what != '}') { |
2469 | char_u *line = ml_get(curr); |
2470 | |
2471 | // Put the cursor on the last character in the last line and make the |
2472 | // motion inclusive. |
2473 | if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0) { |
2474 | curwin->w_cursor.col--; |
2475 | curwin->w_cursor.col -= utf_head_off(line, line + curwin->w_cursor.col); |
2476 | *pincl = true; |
2477 | } |
2478 | } else |
2479 | curwin->w_cursor.col = 0; |
2480 | return true; |
2481 | } |
2482 | |
2483 | /* |
2484 | * check if the string 's' is a nroff macro that is in option 'opt' |
2485 | */ |
2486 | static int inmacro(char_u *opt, char_u *s) |
2487 | { |
2488 | char_u *macro; |
2489 | |
2490 | for (macro = opt; macro[0]; ++macro) { |
2491 | /* Accept two characters in the option being equal to two characters |
2492 | * in the line. A space in the option matches with a space in the |
2493 | * line or the line having ended. */ |
2494 | if ( (macro[0] == s[0] |
2495 | || (macro[0] == ' ' |
2496 | && (s[0] == NUL || s[0] == ' '))) |
2497 | && (macro[1] == s[1] |
2498 | || ((macro[1] == NUL || macro[1] == ' ') |
2499 | && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) |
2500 | break; |
2501 | ++macro; |
2502 | if (macro[0] == NUL) |
2503 | break; |
2504 | } |
2505 | return macro[0] != NUL; |
2506 | } |
2507 | |
2508 | /* |
2509 | * startPS: return TRUE if line 'lnum' is the start of a section or paragraph. |
2510 | * If 'para' is '{' or '}' only check for sections. |
2511 | * If 'both' is TRUE also stop at '}' |
2512 | */ |
2513 | int startPS(linenr_T lnum, int para, int both) |
2514 | { |
2515 | char_u *s; |
2516 | |
2517 | s = ml_get(lnum); |
2518 | if (*s == para || *s == '\f' || (both && *s == '}')) { |
2519 | return true; |
2520 | } |
2521 | if (*s == '.' && (inmacro(p_sections, s + 1) |
2522 | || (!para && inmacro(p_para, s + 1)))) { |
2523 | return true; |
2524 | } |
2525 | return false; |
2526 | } |
2527 | |
2528 | /* |
2529 | * The following routines do the word searches performed by the 'w', 'W', |
2530 | * 'b', 'B', 'e', and 'E' commands. |
2531 | */ |
2532 | |
2533 | /* |
2534 | * To perform these searches, characters are placed into one of three |
2535 | * classes, and transitions between classes determine word boundaries. |
2536 | * |
2537 | * The classes are: |
2538 | * |
2539 | * 0 - white space |
2540 | * 1 - punctuation |
2541 | * 2 or higher - keyword characters (letters, digits and underscore) |
2542 | */ |
2543 | |
2544 | static int cls_bigword; /* TRUE for "W", "B" or "E" */ |
2545 | |
2546 | /* |
2547 | * cls() - returns the class of character at curwin->w_cursor |
2548 | * |
2549 | * If a 'W', 'B', or 'E' motion is being done (cls_bigword == TRUE), chars |
2550 | * from class 2 and higher are reported as class 1 since only white space |
2551 | * boundaries are of interest. |
2552 | */ |
2553 | static int cls(void) |
2554 | { |
2555 | int c; |
2556 | |
2557 | c = gchar_cursor(); |
2558 | if (c == ' ' || c == '\t' || c == NUL) { |
2559 | return 0; |
2560 | } |
2561 | |
2562 | c = utf_class(c); |
2563 | |
2564 | // If cls_bigword is TRUE, report all non-blanks as class 1. |
2565 | if (c != 0 && cls_bigword) { |
2566 | return 1; |
2567 | } |
2568 | return c; |
2569 | } |
2570 | |
2571 | /* |
2572 | * fwd_word(count, type, eol) - move forward one word |
2573 | * |
2574 | * Returns FAIL if the cursor was already at the end of the file. |
2575 | * If eol is TRUE, last word stops at end of line (for operators). |
2576 | */ |
2577 | int |
2578 | fwd_word( |
2579 | long count, |
2580 | int bigword, /* "W", "E" or "B" */ |
2581 | int eol |
2582 | ) |
2583 | { |
2584 | int sclass; /* starting class */ |
2585 | int i; |
2586 | int last_line; |
2587 | |
2588 | curwin->w_cursor.coladd = 0; |
2589 | cls_bigword = bigword; |
2590 | while (--count >= 0) { |
2591 | /* When inside a range of folded lines, move to the last char of the |
2592 | * last line. */ |
2593 | if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) |
2594 | coladvance((colnr_T)MAXCOL); |
2595 | sclass = cls(); |
2596 | |
2597 | /* |
2598 | * We always move at least one character, unless on the last |
2599 | * character in the buffer. |
2600 | */ |
2601 | last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); |
2602 | i = inc_cursor(); |
2603 | if (i == -1 || (i >= 1 && last_line)) /* started at last char in file */ |
2604 | return FAIL; |
2605 | if (i >= 1 && eol && count == 0) /* started at last char in line */ |
2606 | return OK; |
2607 | |
2608 | /* |
2609 | * Go one char past end of current word (if any) |
2610 | */ |
2611 | if (sclass != 0) |
2612 | while (cls() == sclass) { |
2613 | i = inc_cursor(); |
2614 | if (i == -1 || (i >= 1 && eol && count == 0)) |
2615 | return OK; |
2616 | } |
2617 | |
2618 | /* |
2619 | * go to next non-white |
2620 | */ |
2621 | while (cls() == 0) { |
2622 | /* |
2623 | * We'll stop if we land on a blank line |
2624 | */ |
2625 | if (curwin->w_cursor.col == 0 && *get_cursor_line_ptr() == NUL) |
2626 | break; |
2627 | |
2628 | i = inc_cursor(); |
2629 | if (i == -1 || (i >= 1 && eol && count == 0)) |
2630 | return OK; |
2631 | } |
2632 | } |
2633 | return OK; |
2634 | } |
2635 | |
2636 | /* |
2637 | * bck_word() - move backward 'count' words |
2638 | * |
2639 | * If stop is TRUE and we are already on the start of a word, move one less. |
2640 | * |
2641 | * Returns FAIL if top of the file was reached. |
2642 | */ |
2643 | int bck_word(long count, int bigword, int stop) |
2644 | { |
2645 | int sclass; /* starting class */ |
2646 | |
2647 | curwin->w_cursor.coladd = 0; |
2648 | cls_bigword = bigword; |
2649 | while (--count >= 0) { |
2650 | /* When inside a range of folded lines, move to the first char of the |
2651 | * first line. */ |
2652 | if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) |
2653 | curwin->w_cursor.col = 0; |
2654 | sclass = cls(); |
2655 | if (dec_cursor() == -1) /* started at start of file */ |
2656 | return FAIL; |
2657 | |
2658 | if (!stop || sclass == cls() || sclass == 0) { |
2659 | /* |
2660 | * Skip white space before the word. |
2661 | * Stop on an empty line. |
2662 | */ |
2663 | while (cls() == 0) { |
2664 | if (curwin->w_cursor.col == 0 |
2665 | && LINEEMPTY(curwin->w_cursor.lnum)) { |
2666 | goto finished; |
2667 | } |
2668 | if (dec_cursor() == -1) { // hit start of file, stop here |
2669 | return OK; |
2670 | } |
2671 | } |
2672 | |
2673 | /* |
2674 | * Move backward to start of this word. |
2675 | */ |
2676 | if (skip_chars(cls(), BACKWARD)) |
2677 | return OK; |
2678 | } |
2679 | |
2680 | inc_cursor(); /* overshot - forward one */ |
2681 | finished: |
2682 | stop = FALSE; |
2683 | } |
2684 | return OK; |
2685 | } |
2686 | |
2687 | /* |
2688 | * end_word() - move to the end of the word |
2689 | * |
2690 | * There is an apparent bug in the 'e' motion of the real vi. At least on the |
2691 | * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' |
2692 | * motion crosses blank lines. When the real vi crosses a blank line in an |
2693 | * 'e' motion, the cursor is placed on the FIRST character of the next |
2694 | * non-blank line. The 'E' command, however, works correctly. Since this |
2695 | * appears to be a bug, I have not duplicated it here. |
2696 | * |
2697 | * Returns FAIL if end of the file was reached. |
2698 | * |
2699 | * If stop is TRUE and we are already on the end of a word, move one less. |
2700 | * If empty is TRUE stop on an empty line. |
2701 | */ |
2702 | int end_word(long count, int bigword, int stop, int empty) |
2703 | { |
2704 | int sclass; /* starting class */ |
2705 | |
2706 | curwin->w_cursor.coladd = 0; |
2707 | cls_bigword = bigword; |
2708 | while (--count >= 0) { |
2709 | /* When inside a range of folded lines, move to the last char of the |
2710 | * last line. */ |
2711 | if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) |
2712 | coladvance((colnr_T)MAXCOL); |
2713 | sclass = cls(); |
2714 | if (inc_cursor() == -1) |
2715 | return FAIL; |
2716 | |
2717 | /* |
2718 | * If we're in the middle of a word, we just have to move to the end |
2719 | * of it. |
2720 | */ |
2721 | if (cls() == sclass && sclass != 0) { |
2722 | /* |
2723 | * Move forward to end of the current word |
2724 | */ |
2725 | if (skip_chars(sclass, FORWARD)) |
2726 | return FAIL; |
2727 | } else if (!stop || sclass == 0) { |
2728 | /* |
2729 | * We were at the end of a word. Go to the end of the next word. |
2730 | * First skip white space, if 'empty' is TRUE, stop at empty line. |
2731 | */ |
2732 | while (cls() == 0) { |
2733 | if (empty && curwin->w_cursor.col == 0 |
2734 | && LINEEMPTY(curwin->w_cursor.lnum)) { |
2735 | goto finished; |
2736 | } |
2737 | if (inc_cursor() == -1) { // hit end of file, stop here |
2738 | return FAIL; |
2739 | } |
2740 | } |
2741 | |
2742 | /* |
2743 | * Move forward to the end of this word. |
2744 | */ |
2745 | if (skip_chars(cls(), FORWARD)) |
2746 | return FAIL; |
2747 | } |
2748 | dec_cursor(); /* overshot - one char backward */ |
2749 | finished: |
2750 | stop = FALSE; /* we move only one word less */ |
2751 | } |
2752 | return OK; |
2753 | } |
2754 | |
2755 | /* |
2756 | * Move back to the end of the word. |
2757 | * |
2758 | * Returns FAIL if start of the file was reached. |
2759 | */ |
2760 | int |
2761 | bckend_word( |
2762 | long count, |
2763 | int bigword, /* TRUE for "B" */ |
2764 | int eol /* TRUE: stop at end of line. */ |
2765 | ) |
2766 | { |
2767 | int sclass; /* starting class */ |
2768 | int i; |
2769 | |
2770 | curwin->w_cursor.coladd = 0; |
2771 | cls_bigword = bigword; |
2772 | while (--count >= 0) { |
2773 | sclass = cls(); |
2774 | if ((i = dec_cursor()) == -1) |
2775 | return FAIL; |
2776 | if (eol && i == 1) |
2777 | return OK; |
2778 | |
2779 | /* |
2780 | * Move backward to before the start of this word. |
2781 | */ |
2782 | if (sclass != 0) { |
2783 | while (cls() == sclass) |
2784 | if ((i = dec_cursor()) == -1 || (eol && i == 1)) |
2785 | return OK; |
2786 | } |
2787 | |
2788 | /* |
2789 | * Move backward to end of the previous word |
2790 | */ |
2791 | while (cls() == 0) { |
2792 | if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) { |
2793 | break; |
2794 | } |
2795 | if ((i = dec_cursor()) == -1 || (eol && i == 1)) { |
2796 | return OK; |
2797 | } |
2798 | } |
2799 | } |
2800 | return OK; |
2801 | } |
2802 | |
2803 | /* |
2804 | * Skip a row of characters of the same class. |
2805 | * Return TRUE when end-of-file reached, FALSE otherwise. |
2806 | */ |
2807 | static int skip_chars(int cclass, int dir) |
2808 | { |
2809 | while (cls() == cclass) |
2810 | if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) |
2811 | return TRUE; |
2812 | return FALSE; |
2813 | } |
2814 | |
2815 | /* |
2816 | * Go back to the start of the word or the start of white space |
2817 | */ |
2818 | static void back_in_line(void) |
2819 | { |
2820 | int sclass; /* starting class */ |
2821 | |
2822 | sclass = cls(); |
2823 | for (;; ) { |
2824 | if (curwin->w_cursor.col == 0) /* stop at start of line */ |
2825 | break; |
2826 | dec_cursor(); |
2827 | if (cls() != sclass) { /* stop at start of word */ |
2828 | inc_cursor(); |
2829 | break; |
2830 | } |
2831 | } |
2832 | } |
2833 | |
2834 | static void find_first_blank(pos_T *posp) |
2835 | { |
2836 | int c; |
2837 | |
2838 | while (decl(posp) != -1) { |
2839 | c = gchar_pos(posp); |
2840 | if (!ascii_iswhite(c)) { |
2841 | incl(posp); |
2842 | break; |
2843 | } |
2844 | } |
2845 | } |
2846 | |
2847 | /* |
2848 | * Skip count/2 sentences and count/2 separating white spaces. |
2849 | */ |
2850 | static void |
2851 | findsent_forward( |
2852 | long count, |
2853 | int at_start_sent /* cursor is at start of sentence */ |
2854 | ) |
2855 | { |
2856 | while (count--) { |
2857 | findsent(FORWARD, 1L); |
2858 | if (at_start_sent) |
2859 | find_first_blank(&curwin->w_cursor); |
2860 | if (count == 0 || at_start_sent) |
2861 | decl(&curwin->w_cursor); |
2862 | at_start_sent = !at_start_sent; |
2863 | } |
2864 | } |
2865 | |
2866 | /* |
2867 | * Find word under cursor, cursor at end. |
2868 | * Used while an operator is pending, and in Visual mode. |
2869 | */ |
2870 | int |
2871 | current_word( |
2872 | oparg_T *oap, |
2873 | long count, |
2874 | int include, /* TRUE: include word and white space */ |
2875 | int bigword /* FALSE == word, TRUE == WORD */ |
2876 | ) |
2877 | { |
2878 | pos_T start_pos; |
2879 | pos_T pos; |
2880 | bool inclusive = true; |
2881 | int include_white = FALSE; |
2882 | |
2883 | cls_bigword = bigword; |
2884 | clearpos(&start_pos); |
2885 | |
2886 | /* Correct cursor when 'selection' is exclusive */ |
2887 | if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) |
2888 | dec_cursor(); |
2889 | |
2890 | /* |
2891 | * When Visual mode is not active, or when the VIsual area is only one |
2892 | * character, select the word and/or white space under the cursor. |
2893 | */ |
2894 | if (!VIsual_active || equalpos(curwin->w_cursor, VIsual)) { |
2895 | /* |
2896 | * Go to start of current word or white space. |
2897 | */ |
2898 | back_in_line(); |
2899 | start_pos = curwin->w_cursor; |
2900 | |
2901 | /* |
2902 | * If the start is on white space, and white space should be included |
2903 | * (" word"), or start is not on white space, and white space should |
2904 | * not be included ("word"), find end of word. |
2905 | */ |
2906 | if ((cls() == 0) == include) { |
2907 | if (end_word(1L, bigword, TRUE, TRUE) == FAIL) |
2908 | return FAIL; |
2909 | } else { |
2910 | /* |
2911 | * If the start is not on white space, and white space should be |
2912 | * included ("word "), or start is on white space and white |
2913 | * space should not be included (" "), find start of word. |
2914 | * If we end up in the first column of the next line (single char |
2915 | * word) back up to end of the line. |
2916 | */ |
2917 | fwd_word(1L, bigword, TRUE); |
2918 | if (curwin->w_cursor.col == 0) |
2919 | decl(&curwin->w_cursor); |
2920 | else |
2921 | oneleft(); |
2922 | |
2923 | if (include) |
2924 | include_white = TRUE; |
2925 | } |
2926 | |
2927 | if (VIsual_active) { |
2928 | /* should do something when inclusive == false ! */ |
2929 | VIsual = start_pos; |
2930 | redraw_curbuf_later(INVERTED); /* update the inversion */ |
2931 | } else { |
2932 | oap->start = start_pos; |
2933 | oap->motion_type = kMTCharWise; |
2934 | } |
2935 | --count; |
2936 | } |
2937 | |
2938 | /* |
2939 | * When count is still > 0, extend with more objects. |
2940 | */ |
2941 | while (count > 0) { |
2942 | inclusive = true; |
2943 | if (VIsual_active && lt(curwin->w_cursor, VIsual)) { |
2944 | /* |
2945 | * In Visual mode, with cursor at start: move cursor back. |
2946 | */ |
2947 | if (decl(&curwin->w_cursor) == -1) |
2948 | return FAIL; |
2949 | if (include != (cls() != 0)) { |
2950 | if (bck_word(1L, bigword, TRUE) == FAIL) |
2951 | return FAIL; |
2952 | } else { |
2953 | if (bckend_word(1L, bigword, TRUE) == FAIL) |
2954 | return FAIL; |
2955 | (void)incl(&curwin->w_cursor); |
2956 | } |
2957 | } else { |
2958 | /* |
2959 | * Move cursor forward one word and/or white area. |
2960 | */ |
2961 | if (incl(&curwin->w_cursor) == -1) |
2962 | return FAIL; |
2963 | if (include != (cls() == 0)) { |
2964 | if (fwd_word(1L, bigword, TRUE) == FAIL && count > 1) |
2965 | return FAIL; |
2966 | /* |
2967 | * If end is just past a new-line, we don't want to include |
2968 | * the first character on the line. |
2969 | * Put cursor on last char of white. |
2970 | */ |
2971 | if (oneleft() == FAIL) |
2972 | inclusive = false; |
2973 | } else { |
2974 | if (end_word(1L, bigword, TRUE, TRUE) == FAIL) |
2975 | return FAIL; |
2976 | } |
2977 | } |
2978 | --count; |
2979 | } |
2980 | |
2981 | if (include_white && (cls() != 0 |
2982 | || (curwin->w_cursor.col == 0 && !inclusive))) { |
2983 | /* |
2984 | * If we don't include white space at the end, move the start |
2985 | * to include some white space there. This makes "daw" work |
2986 | * better on the last word in a sentence (and "2daw" on last-but-one |
2987 | * word). Also when "2daw" deletes "word." at the end of the line |
2988 | * (cursor is at start of next line). |
2989 | * But don't delete white space at start of line (indent). |
2990 | */ |
2991 | pos = curwin->w_cursor; /* save cursor position */ |
2992 | curwin->w_cursor = start_pos; |
2993 | if (oneleft() == OK) { |
2994 | back_in_line(); |
2995 | if (cls() == 0 && curwin->w_cursor.col > 0) { |
2996 | if (VIsual_active) |
2997 | VIsual = curwin->w_cursor; |
2998 | else |
2999 | oap->start = curwin->w_cursor; |
3000 | } |
3001 | } |
3002 | curwin->w_cursor = pos; /* put cursor back at end */ |
3003 | } |
3004 | |
3005 | if (VIsual_active) { |
3006 | if (*p_sel == 'e' && inclusive && ltoreq(VIsual, curwin->w_cursor)) |
3007 | inc_cursor(); |
3008 | if (VIsual_mode == 'V') { |
3009 | VIsual_mode = 'v'; |
3010 | redraw_cmdline = TRUE; /* show mode later */ |
3011 | } |
3012 | } else |
3013 | oap->inclusive = inclusive; |
3014 | |
3015 | return OK; |
3016 | } |
3017 | |
3018 | /* |
3019 | * Find sentence(s) under the cursor, cursor at end. |
3020 | * When Visual active, extend it by one or more sentences. |
3021 | */ |
3022 | int current_sent(oparg_T *oap, long count, int include) |
3023 | { |
3024 | pos_T start_pos; |
3025 | pos_T pos; |
3026 | int start_blank; |
3027 | int c; |
3028 | int at_start_sent; |
3029 | long ncount; |
3030 | |
3031 | start_pos = curwin->w_cursor; |
3032 | pos = start_pos; |
3033 | findsent(FORWARD, 1L); /* Find start of next sentence. */ |
3034 | |
3035 | /* |
3036 | * When the Visual area is bigger than one character: Extend it. |
3037 | */ |
3038 | if (VIsual_active && !equalpos(start_pos, VIsual)) { |
3039 | extend: |
3040 | if (lt(start_pos, VIsual)) { |
3041 | /* |
3042 | * Cursor at start of Visual area. |
3043 | * Find out where we are: |
3044 | * - in the white space before a sentence |
3045 | * - in a sentence or just after it |
3046 | * - at the start of a sentence |
3047 | */ |
3048 | at_start_sent = TRUE; |
3049 | decl(&pos); |
3050 | while (lt(pos, curwin->w_cursor)) { |
3051 | c = gchar_pos(&pos); |
3052 | if (!ascii_iswhite(c)) { |
3053 | at_start_sent = FALSE; |
3054 | break; |
3055 | } |
3056 | incl(&pos); |
3057 | } |
3058 | if (!at_start_sent) { |
3059 | findsent(BACKWARD, 1L); |
3060 | if (equalpos(curwin->w_cursor, start_pos)) |
3061 | at_start_sent = TRUE; /* exactly at start of sentence */ |
3062 | else |
3063 | /* inside a sentence, go to its end (start of next) */ |
3064 | findsent(FORWARD, 1L); |
3065 | } |
3066 | if (include) /* "as" gets twice as much as "is" */ |
3067 | count *= 2; |
3068 | while (count--) { |
3069 | if (at_start_sent) |
3070 | find_first_blank(&curwin->w_cursor); |
3071 | c = gchar_cursor(); |
3072 | if (!at_start_sent || (!include && !ascii_iswhite(c))) |
3073 | findsent(BACKWARD, 1L); |
3074 | at_start_sent = !at_start_sent; |
3075 | } |
3076 | } else { |
3077 | /* |
3078 | * Cursor at end of Visual area. |
3079 | * Find out where we are: |
3080 | * - just before a sentence |
3081 | * - just before or in the white space before a sentence |
3082 | * - in a sentence |
3083 | */ |
3084 | incl(&pos); |
3085 | at_start_sent = TRUE; |
3086 | if (!equalpos(pos, curwin->w_cursor)) { /* not just before a sentence */ |
3087 | at_start_sent = FALSE; |
3088 | while (lt(pos, curwin->w_cursor)) { |
3089 | c = gchar_pos(&pos); |
3090 | if (!ascii_iswhite(c)) { |
3091 | at_start_sent = TRUE; |
3092 | break; |
3093 | } |
3094 | incl(&pos); |
3095 | } |
3096 | if (at_start_sent) /* in the sentence */ |
3097 | findsent(BACKWARD, 1L); |
3098 | else /* in/before white before a sentence */ |
3099 | curwin->w_cursor = start_pos; |
3100 | } |
3101 | |
3102 | if (include) /* "as" gets twice as much as "is" */ |
3103 | count *= 2; |
3104 | findsent_forward(count, at_start_sent); |
3105 | if (*p_sel == 'e') |
3106 | ++curwin->w_cursor.col; |
3107 | } |
3108 | return OK; |
3109 | } |
3110 | |
3111 | /* |
3112 | * If the cursor started on a blank, check if it is just before the start |
3113 | * of the next sentence. |
3114 | */ |
3115 | while (c = gchar_pos(&pos), ascii_iswhite(c)) |
3116 | incl(&pos); |
3117 | if (equalpos(pos, curwin->w_cursor)) { |
3118 | start_blank = TRUE; |
3119 | find_first_blank(&start_pos); /* go back to first blank */ |
3120 | } else { |
3121 | start_blank = FALSE; |
3122 | findsent(BACKWARD, 1L); |
3123 | start_pos = curwin->w_cursor; |
3124 | } |
3125 | if (include) |
3126 | ncount = count * 2; |
3127 | else { |
3128 | ncount = count; |
3129 | if (start_blank) |
3130 | --ncount; |
3131 | } |
3132 | if (ncount > 0) |
3133 | findsent_forward(ncount, TRUE); |
3134 | else |
3135 | decl(&curwin->w_cursor); |
3136 | |
3137 | if (include) { |
3138 | /* |
3139 | * If the blank in front of the sentence is included, exclude the |
3140 | * blanks at the end of the sentence, go back to the first blank. |
3141 | * If there are no trailing blanks, try to include leading blanks. |
3142 | */ |
3143 | if (start_blank) { |
3144 | find_first_blank(&curwin->w_cursor); |
3145 | c = gchar_pos(&curwin->w_cursor); |
3146 | if (ascii_iswhite(c)) |
3147 | decl(&curwin->w_cursor); |
3148 | } else if (c = gchar_cursor(), !ascii_iswhite(c)) |
3149 | find_first_blank(&start_pos); |
3150 | } |
3151 | |
3152 | if (VIsual_active) { |
3153 | /* Avoid getting stuck with "is" on a single space before a sentence. */ |
3154 | if (equalpos(start_pos, curwin->w_cursor)) |
3155 | goto extend; |
3156 | if (*p_sel == 'e') |
3157 | ++curwin->w_cursor.col; |
3158 | VIsual = start_pos; |
3159 | VIsual_mode = 'v'; |
3160 | redraw_cmdline = true; // show mode later |
3161 | redraw_curbuf_later(INVERTED); // update the inversion |
3162 | } else { |
3163 | /* include a newline after the sentence, if there is one */ |
3164 | if (incl(&curwin->w_cursor) == -1) |
3165 | oap->inclusive = true; |
3166 | else |
3167 | oap->inclusive = false; |
3168 | oap->start = start_pos; |
3169 | oap->motion_type = kMTCharWise; |
3170 | } |
3171 | return OK; |
3172 | } |
3173 | |
3174 | /* |
3175 | * Find block under the cursor, cursor at end. |
3176 | * "what" and "other" are two matching parenthesis/brace/etc. |
3177 | */ |
3178 | int |
3179 | current_block( |
3180 | oparg_T *oap, |
3181 | long count, |
3182 | int include, /* TRUE == include white space */ |
3183 | int what, /* '(', '{', etc. */ |
3184 | int other /* ')', '}', etc. */ |
3185 | ) |
3186 | { |
3187 | pos_T old_pos; |
3188 | pos_T *pos = NULL; |
3189 | pos_T start_pos; |
3190 | pos_T *end_pos; |
3191 | pos_T old_start, old_end; |
3192 | char_u *save_cpo; |
3193 | int sol = FALSE; /* '{' at start of line */ |
3194 | |
3195 | old_pos = curwin->w_cursor; |
3196 | old_end = curwin->w_cursor; /* remember where we started */ |
3197 | old_start = old_end; |
3198 | |
3199 | /* |
3200 | * If we start on '(', '{', ')', '}', etc., use the whole block inclusive. |
3201 | */ |
3202 | if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { |
3203 | setpcmark(); |
3204 | if (what == '{') /* ignore indent */ |
3205 | while (inindent(1)) |
3206 | if (inc_cursor() != 0) |
3207 | break; |
3208 | if (gchar_cursor() == what) |
3209 | /* cursor on '(' or '{', move cursor just after it */ |
3210 | ++curwin->w_cursor.col; |
3211 | } else if (lt(VIsual, curwin->w_cursor)) { |
3212 | old_start = VIsual; |
3213 | curwin->w_cursor = VIsual; /* cursor at low end of Visual */ |
3214 | } else |
3215 | old_end = VIsual; |
3216 | |
3217 | // Search backwards for unclosed '(', '{', etc.. |
3218 | // Put this position in start_pos. |
3219 | // Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the |
3220 | // user wants. |
3221 | save_cpo = p_cpo; |
3222 | p_cpo = (char_u *)(vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%" ); |
3223 | while (count-- > 0) { |
3224 | if ((pos = findmatch(NULL, what)) == NULL) { |
3225 | break; |
3226 | } |
3227 | curwin->w_cursor = *pos; |
3228 | start_pos = *pos; // the findmatch for end_pos will overwrite *pos |
3229 | } |
3230 | p_cpo = save_cpo; |
3231 | |
3232 | /* |
3233 | * Search for matching ')', '}', etc. |
3234 | * Put this position in curwin->w_cursor. |
3235 | */ |
3236 | if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) { |
3237 | curwin->w_cursor = old_pos; |
3238 | return FAIL; |
3239 | } |
3240 | curwin->w_cursor = *end_pos; |
3241 | |
3242 | // Try to exclude the '(', '{', ')', '}', etc. when "include" is FALSE. |
3243 | // If the ending '}', ')' or ']' is only preceded by indent, skip that |
3244 | // indent. But only if the resulting area is not smaller than what we |
3245 | // started with. |
3246 | while (!include) { |
3247 | incl(&start_pos); |
3248 | sol = (curwin->w_cursor.col == 0); |
3249 | decl(&curwin->w_cursor); |
3250 | while (inindent(1)) { |
3251 | sol = TRUE; |
3252 | if (decl(&curwin->w_cursor) != 0) { |
3253 | break; |
3254 | } |
3255 | } |
3256 | |
3257 | /* |
3258 | * In Visual mode, when the resulting area is not bigger than what we |
3259 | * started with, extend it to the next block, and then exclude again. |
3260 | */ |
3261 | if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor) |
3262 | && VIsual_active) { |
3263 | curwin->w_cursor = old_start; |
3264 | decl(&curwin->w_cursor); |
3265 | if ((pos = findmatch(NULL, what)) == NULL) { |
3266 | curwin->w_cursor = old_pos; |
3267 | return FAIL; |
3268 | } |
3269 | start_pos = *pos; |
3270 | curwin->w_cursor = *pos; |
3271 | if ((end_pos = findmatch(NULL, other)) == NULL) { |
3272 | curwin->w_cursor = old_pos; |
3273 | return FAIL; |
3274 | } |
3275 | curwin->w_cursor = *end_pos; |
3276 | } else |
3277 | break; |
3278 | } |
3279 | |
3280 | if (VIsual_active) { |
3281 | if (*p_sel == 'e') { |
3282 | inc(&curwin->w_cursor); |
3283 | } |
3284 | if (sol && gchar_cursor() != NUL) { |
3285 | inc(&curwin->w_cursor); // include the line break |
3286 | } |
3287 | VIsual = start_pos; |
3288 | VIsual_mode = 'v'; |
3289 | redraw_curbuf_later(INVERTED); /* update the inversion */ |
3290 | showmode(); |
3291 | } else { |
3292 | oap->start = start_pos; |
3293 | oap->motion_type = kMTCharWise; |
3294 | oap->inclusive = false; |
3295 | if (sol) |
3296 | incl(&curwin->w_cursor); |
3297 | else if (ltoreq(start_pos, curwin->w_cursor)) |
3298 | /* Include the character under the cursor. */ |
3299 | oap->inclusive = true; |
3300 | else |
3301 | /* End is before the start (no text in between <>, [], etc.): don't |
3302 | * operate on any text. */ |
3303 | curwin->w_cursor = start_pos; |
3304 | } |
3305 | |
3306 | return OK; |
3307 | } |
3308 | |
3309 | |
3310 | /* |
3311 | * Return TRUE if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". |
3312 | * When "end_tag" is TRUE return TRUE if the cursor is on "</aaa>". |
3313 | */ |
3314 | static int in_html_tag(int end_tag) |
3315 | { |
3316 | char_u *line = get_cursor_line_ptr(); |
3317 | char_u *p; |
3318 | int c; |
3319 | int lc = NUL; |
3320 | pos_T pos; |
3321 | |
3322 | for (p = line + curwin->w_cursor.col; p > line; ) { |
3323 | if (*p == '<') { // find '<' under/before cursor |
3324 | break; |
3325 | } |
3326 | MB_PTR_BACK(line, p); |
3327 | if (*p == '>') { // find '>' before cursor |
3328 | break; |
3329 | } |
3330 | } |
3331 | if (*p != '<') { |
3332 | return false; |
3333 | } |
3334 | |
3335 | pos.lnum = curwin->w_cursor.lnum; |
3336 | pos.col = (colnr_T)(p - line); |
3337 | |
3338 | MB_PTR_ADV(p); |
3339 | if (end_tag) { |
3340 | // check that there is a '/' after the '<' |
3341 | return *p == '/'; |
3342 | } |
3343 | |
3344 | /* check that there is no '/' after the '<' */ |
3345 | if (*p == '/') |
3346 | return FALSE; |
3347 | |
3348 | /* check that the matching '>' is not preceded by '/' */ |
3349 | for (;; ) { |
3350 | if (inc(&pos) < 0) |
3351 | return FALSE; |
3352 | c = *ml_get_pos(&pos); |
3353 | if (c == '>') |
3354 | break; |
3355 | lc = c; |
3356 | } |
3357 | return lc != '/'; |
3358 | } |
3359 | |
3360 | /* |
3361 | * Find tag block under the cursor, cursor at end. |
3362 | */ |
3363 | int |
3364 | current_tagblock( |
3365 | oparg_T *oap, |
3366 | long count_arg, |
3367 | bool include // true == include white space |
3368 | ) |
3369 | { |
3370 | long count = count_arg; |
3371 | pos_T old_pos; |
3372 | pos_T start_pos; |
3373 | pos_T end_pos; |
3374 | pos_T old_start, old_end; |
3375 | char_u *spat, *epat; |
3376 | char_u *p; |
3377 | char_u *cp; |
3378 | int len; |
3379 | bool do_include = include; |
3380 | bool save_p_ws = p_ws; |
3381 | int retval = FAIL; |
3382 | int is_inclusive = true; |
3383 | |
3384 | p_ws = false; |
3385 | |
3386 | old_pos = curwin->w_cursor; |
3387 | old_end = curwin->w_cursor; /* remember where we started */ |
3388 | old_start = old_end; |
3389 | if (!VIsual_active || *p_sel == 'e') |
3390 | decl(&old_end); /* old_end is inclusive */ |
3391 | |
3392 | /* |
3393 | * If we start on "<aaa>" select that block. |
3394 | */ |
3395 | if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { |
3396 | setpcmark(); |
3397 | |
3398 | /* ignore indent */ |
3399 | while (inindent(1)) |
3400 | if (inc_cursor() != 0) |
3401 | break; |
3402 | |
3403 | if (in_html_tag(FALSE)) { |
3404 | /* cursor on start tag, move to its '>' */ |
3405 | while (*get_cursor_pos_ptr() != '>') |
3406 | if (inc_cursor() < 0) |
3407 | break; |
3408 | } else if (in_html_tag(TRUE)) { |
3409 | /* cursor on end tag, move to just before it */ |
3410 | while (*get_cursor_pos_ptr() != '<') |
3411 | if (dec_cursor() < 0) |
3412 | break; |
3413 | dec_cursor(); |
3414 | old_end = curwin->w_cursor; |
3415 | } |
3416 | } else if (lt(VIsual, curwin->w_cursor)) { |
3417 | old_start = VIsual; |
3418 | curwin->w_cursor = VIsual; /* cursor at low end of Visual */ |
3419 | } else |
3420 | old_end = VIsual; |
3421 | |
3422 | again: |
3423 | /* |
3424 | * Search backwards for unclosed "<aaa>". |
3425 | * Put this position in start_pos. |
3426 | */ |
3427 | for (long n = 0; n < count; n++) { |
3428 | if (do_searchpair( |
3429 | (char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)" , |
3430 | (char_u *)"" , |
3431 | (char_u *)"</[^>]*>" , BACKWARD, NULL, 0, |
3432 | NULL, (linenr_T)0, 0L) <= 0) { |
3433 | curwin->w_cursor = old_pos; |
3434 | goto theend; |
3435 | } |
3436 | } |
3437 | start_pos = curwin->w_cursor; |
3438 | |
3439 | /* |
3440 | * Search for matching "</aaa>". First isolate the "aaa". |
3441 | */ |
3442 | inc_cursor(); |
3443 | p = get_cursor_pos_ptr(); |
3444 | for (cp = p; |
3445 | *cp != NUL && *cp != '>' && !ascii_iswhite(*cp); |
3446 | MB_PTR_ADV(cp)) { |
3447 | } |
3448 | len = (int)(cp - p); |
3449 | if (len == 0) { |
3450 | curwin->w_cursor = old_pos; |
3451 | goto theend; |
3452 | } |
3453 | spat = xmalloc(len + 31); |
3454 | epat = xmalloc(len + 9); |
3455 | sprintf((char *)spat, "<%.*s\\>\\%%(\\s\\_[^>]\\{-}[^/]>\\|>\\)\\c" , len, p); |
3456 | sprintf((char *)epat, "</%.*s>\\c" , len, p); |
3457 | |
3458 | const int r = do_searchpair(spat, (char_u *)"" , epat, FORWARD, NULL, |
3459 | 0, NULL, (linenr_T)0, 0L); |
3460 | |
3461 | xfree(spat); |
3462 | xfree(epat); |
3463 | |
3464 | if (r < 1 || lt(curwin->w_cursor, old_end)) { |
3465 | /* Can't find other end or it's before the previous end. Could be a |
3466 | * HTML tag that doesn't have a matching end. Search backwards for |
3467 | * another starting tag. */ |
3468 | count = 1; |
3469 | curwin->w_cursor = start_pos; |
3470 | goto again; |
3471 | } |
3472 | |
3473 | if (do_include) { |
3474 | // Include up to the '>'. |
3475 | while (*get_cursor_pos_ptr() != '>') { |
3476 | if (inc_cursor() < 0) { |
3477 | break; |
3478 | } |
3479 | } |
3480 | } else { |
3481 | char_u *c = get_cursor_pos_ptr(); |
3482 | // Exclude the '<' of the end tag. |
3483 | // If the closing tag is on new line, do not decrement cursor, but make |
3484 | // operation exclusive, so that the linefeed will be selected |
3485 | if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) { |
3486 | // do not decrement cursor |
3487 | is_inclusive = false; |
3488 | } else if (*c == '<') { |
3489 | dec_cursor(); |
3490 | } |
3491 | } |
3492 | end_pos = curwin->w_cursor; |
3493 | |
3494 | if (!do_include) { |
3495 | /* Exclude the start tag. */ |
3496 | curwin->w_cursor = start_pos; |
3497 | while (inc_cursor() >= 0) |
3498 | if (*get_cursor_pos_ptr() == '>') { |
3499 | inc_cursor(); |
3500 | start_pos = curwin->w_cursor; |
3501 | break; |
3502 | } |
3503 | curwin->w_cursor = end_pos; |
3504 | |
3505 | // If we are in Visual mode and now have the same text as before set |
3506 | // "do_include" and try again. |
3507 | if (VIsual_active |
3508 | && equalpos(start_pos, old_start) |
3509 | && equalpos(end_pos, old_end)) { |
3510 | do_include = true; |
3511 | curwin->w_cursor = old_start; |
3512 | count = count_arg; |
3513 | goto again; |
3514 | } |
3515 | } |
3516 | |
3517 | if (VIsual_active) { |
3518 | /* If the end is before the start there is no text between tags, select |
3519 | * the char under the cursor. */ |
3520 | if (lt(end_pos, start_pos)) { |
3521 | curwin->w_cursor = start_pos; |
3522 | } else if (*p_sel == 'e') { |
3523 | inc_cursor(); |
3524 | } |
3525 | VIsual = start_pos; |
3526 | VIsual_mode = 'v'; |
3527 | redraw_curbuf_later(INVERTED); /* update the inversion */ |
3528 | showmode(); |
3529 | } else { |
3530 | oap->start = start_pos; |
3531 | oap->motion_type = kMTCharWise; |
3532 | if (lt(end_pos, start_pos)) { |
3533 | /* End is before the start: there is no text between tags; operate |
3534 | * on an empty area. */ |
3535 | curwin->w_cursor = start_pos; |
3536 | oap->inclusive = false; |
3537 | } else { |
3538 | oap->inclusive = is_inclusive; |
3539 | } |
3540 | } |
3541 | retval = OK; |
3542 | |
3543 | theend: |
3544 | p_ws = save_p_ws; |
3545 | return retval; |
3546 | } |
3547 | |
3548 | int |
3549 | current_par( |
3550 | oparg_T *oap, |
3551 | long count, |
3552 | int include, /* TRUE == include white space */ |
3553 | int type /* 'p' for paragraph, 'S' for section */ |
3554 | ) |
3555 | { |
3556 | linenr_T start_lnum; |
3557 | linenr_T end_lnum; |
3558 | int white_in_front; |
3559 | int dir; |
3560 | int start_is_white; |
3561 | int prev_start_is_white; |
3562 | int retval = OK; |
3563 | int do_white = FALSE; |
3564 | int t; |
3565 | int i; |
3566 | |
3567 | if (type == 'S') /* not implemented yet */ |
3568 | return FAIL; |
3569 | |
3570 | start_lnum = curwin->w_cursor.lnum; |
3571 | |
3572 | /* |
3573 | * When visual area is more than one line: extend it. |
3574 | */ |
3575 | if (VIsual_active && start_lnum != VIsual.lnum) { |
3576 | extend: |
3577 | if (start_lnum < VIsual.lnum) |
3578 | dir = BACKWARD; |
3579 | else |
3580 | dir = FORWARD; |
3581 | for (i = count; --i >= 0; ) { |
3582 | if (start_lnum == |
3583 | (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) { |
3584 | retval = FAIL; |
3585 | break; |
3586 | } |
3587 | |
3588 | prev_start_is_white = -1; |
3589 | for (t = 0; t < 2; ++t) { |
3590 | start_lnum += dir; |
3591 | start_is_white = linewhite(start_lnum); |
3592 | if (prev_start_is_white == start_is_white) { |
3593 | start_lnum -= dir; |
3594 | break; |
3595 | } |
3596 | for (;; ) { |
3597 | if (start_lnum == (dir == BACKWARD |
3598 | ? 1 : curbuf->b_ml.ml_line_count)) |
3599 | break; |
3600 | if (start_is_white != linewhite(start_lnum + dir) |
3601 | || (!start_is_white |
3602 | && startPS(start_lnum + (dir > 0 |
3603 | ? 1 : 0), 0, 0))) |
3604 | break; |
3605 | start_lnum += dir; |
3606 | } |
3607 | if (!include) |
3608 | break; |
3609 | if (start_lnum == (dir == BACKWARD |
3610 | ? 1 : curbuf->b_ml.ml_line_count)) |
3611 | break; |
3612 | prev_start_is_white = start_is_white; |
3613 | } |
3614 | } |
3615 | curwin->w_cursor.lnum = start_lnum; |
3616 | curwin->w_cursor.col = 0; |
3617 | return retval; |
3618 | } |
3619 | |
3620 | /* |
3621 | * First move back to the start_lnum of the paragraph or white lines |
3622 | */ |
3623 | white_in_front = linewhite(start_lnum); |
3624 | while (start_lnum > 1) { |
3625 | if (white_in_front) { /* stop at first white line */ |
3626 | if (!linewhite(start_lnum - 1)) |
3627 | break; |
3628 | } else { /* stop at first non-white line of start of paragraph */ |
3629 | if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) |
3630 | break; |
3631 | } |
3632 | --start_lnum; |
3633 | } |
3634 | |
3635 | /* |
3636 | * Move past the end of any white lines. |
3637 | */ |
3638 | end_lnum = start_lnum; |
3639 | while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) |
3640 | ++end_lnum; |
3641 | |
3642 | --end_lnum; |
3643 | i = count; |
3644 | if (!include && white_in_front) |
3645 | --i; |
3646 | while (i--) { |
3647 | if (end_lnum == curbuf->b_ml.ml_line_count) |
3648 | return FAIL; |
3649 | |
3650 | if (!include) |
3651 | do_white = linewhite(end_lnum + 1); |
3652 | |
3653 | if (include || !do_white) { |
3654 | ++end_lnum; |
3655 | /* |
3656 | * skip to end of paragraph |
3657 | */ |
3658 | while (end_lnum < curbuf->b_ml.ml_line_count |
3659 | && !linewhite(end_lnum + 1) |
3660 | && !startPS(end_lnum + 1, 0, 0)) |
3661 | ++end_lnum; |
3662 | } |
3663 | |
3664 | if (i == 0 && white_in_front && include) |
3665 | break; |
3666 | |
3667 | /* |
3668 | * skip to end of white lines after paragraph |
3669 | */ |
3670 | if (include || do_white) |
3671 | while (end_lnum < curbuf->b_ml.ml_line_count |
3672 | && linewhite(end_lnum + 1)) |
3673 | ++end_lnum; |
3674 | } |
3675 | |
3676 | /* |
3677 | * If there are no empty lines at the end, try to find some empty lines at |
3678 | * the start (unless that has been done already). |
3679 | */ |
3680 | if (!white_in_front && !linewhite(end_lnum) && include) |
3681 | while (start_lnum > 1 && linewhite(start_lnum - 1)) |
3682 | --start_lnum; |
3683 | |
3684 | if (VIsual_active) { |
3685 | // Problem: when doing "Vipipip" nothing happens in a single white |
3686 | // line, we get stuck there. Trap this here. |
3687 | if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) { |
3688 | goto extend; |
3689 | } |
3690 | if (VIsual.lnum != start_lnum) { |
3691 | VIsual.lnum = start_lnum; |
3692 | VIsual.col = 0; |
3693 | } |
3694 | VIsual_mode = 'V'; |
3695 | redraw_curbuf_later(INVERTED); /* update the inversion */ |
3696 | showmode(); |
3697 | } else { |
3698 | oap->start.lnum = start_lnum; |
3699 | oap->start.col = 0; |
3700 | oap->motion_type = kMTLineWise; |
3701 | } |
3702 | curwin->w_cursor.lnum = end_lnum; |
3703 | curwin->w_cursor.col = 0; |
3704 | |
3705 | return OK; |
3706 | } |
3707 | |
3708 | |
3709 | /* |
3710 | * Search quote char from string line[col]. |
3711 | * Quote character escaped by one of the characters in "escape" is not counted |
3712 | * as a quote. |
3713 | * Returns column number of "quotechar" or -1 when not found. |
3714 | */ |
3715 | static int |
3716 | find_next_quote( |
3717 | char_u *line, |
3718 | int col, |
3719 | int quotechar, |
3720 | char_u *escape /* escape characters, can be NULL */ |
3721 | ) |
3722 | { |
3723 | int c; |
3724 | |
3725 | for (;; ) { |
3726 | c = line[col]; |
3727 | if (c == NUL) { |
3728 | return -1; |
3729 | } else if (escape != NULL && vim_strchr(escape, c)) { |
3730 | col++; |
3731 | } else if (c == quotechar) { |
3732 | break; |
3733 | } |
3734 | col += mb_ptr2len(line + col); |
3735 | } |
3736 | return col; |
3737 | } |
3738 | |
3739 | /* |
3740 | * Search backwards in "line" from column "col_start" to find "quotechar". |
3741 | * Quote character escaped by one of the characters in "escape" is not counted |
3742 | * as a quote. |
3743 | * Return the found column or zero. |
3744 | */ |
3745 | static int |
3746 | find_prev_quote( |
3747 | char_u *line, |
3748 | int col_start, |
3749 | int quotechar, |
3750 | char_u *escape /* escape characters, can be NULL */ |
3751 | ) |
3752 | { |
3753 | int n; |
3754 | |
3755 | while (col_start > 0) { |
3756 | col_start--; |
3757 | col_start -= utf_head_off(line, line + col_start); |
3758 | n = 0; |
3759 | if (escape != NULL) |
3760 | while (col_start - n > 0 && vim_strchr(escape, |
3761 | line[col_start - n - 1]) != NULL) |
3762 | ++n; |
3763 | if (n & 1) |
3764 | col_start -= n; /* uneven number of escape chars, skip it */ |
3765 | else if (line[col_start] == quotechar) |
3766 | break; |
3767 | } |
3768 | return col_start; |
3769 | } |
3770 | |
3771 | /* |
3772 | * Find quote under the cursor, cursor at end. |
3773 | * Returns TRUE if found, else FALSE. |
3774 | */ |
3775 | int |
3776 | current_quote( |
3777 | oparg_T *oap, |
3778 | long count, |
3779 | int include, /* TRUE == include quote char */ |
3780 | int quotechar /* Quote character */ |
3781 | ) |
3782 | { |
3783 | char_u *line = get_cursor_line_ptr(); |
3784 | int col_end; |
3785 | int col_start = curwin->w_cursor.col; |
3786 | bool inclusive = false; |
3787 | int vis_empty = true; // Visual selection <= 1 char |
3788 | int vis_bef_curs = false; // Visual starts before cursor |
3789 | int inside_quotes = false; // Looks like "i'" done before |
3790 | int selected_quote = false; // Has quote inside selection |
3791 | int i; |
3792 | int restore_vis_bef = false; // resotre VIsual on abort |
3793 | |
3794 | // Correct cursor when 'selection' is "exclusive". |
3795 | if (VIsual_active) { |
3796 | // this only works within one line |
3797 | if (VIsual.lnum != curwin->w_cursor.lnum) { |
3798 | return false; |
3799 | } |
3800 | |
3801 | vis_bef_curs = lt(VIsual, curwin->w_cursor); |
3802 | if (*p_sel == 'e') { |
3803 | if (!vis_bef_curs) { |
3804 | // VIsual needs to be start of Visual selection. |
3805 | pos_T t = curwin->w_cursor; |
3806 | |
3807 | curwin->w_cursor = VIsual; |
3808 | VIsual = t; |
3809 | vis_bef_curs = true; |
3810 | restore_vis_bef = true; |
3811 | } |
3812 | dec_cursor(); |
3813 | } |
3814 | vis_empty = equalpos(VIsual, curwin->w_cursor); |
3815 | } |
3816 | |
3817 | if (!vis_empty) { |
3818 | /* Check if the existing selection exactly spans the text inside |
3819 | * quotes. */ |
3820 | if (vis_bef_curs) { |
3821 | inside_quotes = VIsual.col > 0 |
3822 | && line[VIsual.col - 1] == quotechar |
3823 | && line[curwin->w_cursor.col] != NUL |
3824 | && line[curwin->w_cursor.col + 1] == quotechar; |
3825 | i = VIsual.col; |
3826 | col_end = curwin->w_cursor.col; |
3827 | } else { |
3828 | inside_quotes = curwin->w_cursor.col > 0 |
3829 | && line[curwin->w_cursor.col - 1] == quotechar |
3830 | && line[VIsual.col] != NUL |
3831 | && line[VIsual.col + 1] == quotechar; |
3832 | i = curwin->w_cursor.col; |
3833 | col_end = VIsual.col; |
3834 | } |
3835 | |
3836 | /* Find out if we have a quote in the selection. */ |
3837 | while (i <= col_end) |
3838 | if (line[i++] == quotechar) { |
3839 | selected_quote = TRUE; |
3840 | break; |
3841 | } |
3842 | } |
3843 | |
3844 | if (!vis_empty && line[col_start] == quotechar) { |
3845 | /* Already selecting something and on a quote character. Find the |
3846 | * next quoted string. */ |
3847 | if (vis_bef_curs) { |
3848 | /* Assume we are on a closing quote: move to after the next |
3849 | * opening quote. */ |
3850 | col_start = find_next_quote(line, col_start + 1, quotechar, NULL); |
3851 | if (col_start < 0) { |
3852 | goto abort_search; |
3853 | } |
3854 | col_end = find_next_quote(line, col_start + 1, quotechar, |
3855 | curbuf->b_p_qe); |
3856 | if (col_end < 0) { |
3857 | /* We were on a starting quote perhaps? */ |
3858 | col_end = col_start; |
3859 | col_start = curwin->w_cursor.col; |
3860 | } |
3861 | } else { |
3862 | col_end = find_prev_quote(line, col_start, quotechar, NULL); |
3863 | if (line[col_end] != quotechar) { |
3864 | goto abort_search; |
3865 | } |
3866 | col_start = find_prev_quote(line, col_end, quotechar, |
3867 | curbuf->b_p_qe); |
3868 | if (line[col_start] != quotechar) { |
3869 | /* We were on an ending quote perhaps? */ |
3870 | col_start = col_end; |
3871 | col_end = curwin->w_cursor.col; |
3872 | } |
3873 | } |
3874 | } else if (line[col_start] == quotechar |
3875 | || !vis_empty |
3876 | ) { |
3877 | int first_col = col_start; |
3878 | |
3879 | if (!vis_empty) { |
3880 | if (vis_bef_curs) |
3881 | first_col = find_next_quote(line, col_start, quotechar, NULL); |
3882 | else |
3883 | first_col = find_prev_quote(line, col_start, quotechar, NULL); |
3884 | } |
3885 | /* The cursor is on a quote, we don't know if it's the opening or |
3886 | * closing quote. Search from the start of the line to find out. |
3887 | * Also do this when there is a Visual area, a' may leave the cursor |
3888 | * in between two strings. */ |
3889 | col_start = 0; |
3890 | for (;; ) { |
3891 | /* Find open quote character. */ |
3892 | col_start = find_next_quote(line, col_start, quotechar, NULL); |
3893 | if (col_start < 0 || col_start > first_col) { |
3894 | goto abort_search; |
3895 | } |
3896 | // Find close quote character. |
3897 | col_end = find_next_quote(line, col_start + 1, quotechar, |
3898 | curbuf->b_p_qe); |
3899 | if (col_end < 0) { |
3900 | goto abort_search; |
3901 | } |
3902 | // If is cursor between start and end quote character, it is |
3903 | // target text object. |
3904 | if (col_start <= first_col && first_col <= col_end) { |
3905 | break; |
3906 | } |
3907 | col_start = col_end + 1; |
3908 | } |
3909 | } else { |
3910 | /* Search backward for a starting quote. */ |
3911 | col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe); |
3912 | if (line[col_start] != quotechar) { |
3913 | /* No quote before the cursor, look after the cursor. */ |
3914 | col_start = find_next_quote(line, col_start, quotechar, NULL); |
3915 | if (col_start < 0) { |
3916 | goto abort_search; |
3917 | } |
3918 | } |
3919 | |
3920 | /* Find close quote character. */ |
3921 | col_end = find_next_quote(line, col_start + 1, quotechar, |
3922 | curbuf->b_p_qe); |
3923 | if (col_end < 0) { |
3924 | goto abort_search; |
3925 | } |
3926 | } |
3927 | |
3928 | /* When "include" is TRUE, include spaces after closing quote or before |
3929 | * the starting quote. */ |
3930 | if (include) { |
3931 | if (ascii_iswhite(line[col_end + 1])) |
3932 | while (ascii_iswhite(line[col_end + 1])) |
3933 | ++col_end; |
3934 | else |
3935 | while (col_start > 0 && ascii_iswhite(line[col_start - 1])) |
3936 | --col_start; |
3937 | } |
3938 | |
3939 | /* Set start position. After vi" another i" must include the ". |
3940 | * For v2i" include the quotes. */ |
3941 | if (!include && count < 2 |
3942 | && (vis_empty || !inside_quotes) |
3943 | ) |
3944 | ++col_start; |
3945 | curwin->w_cursor.col = col_start; |
3946 | if (VIsual_active) { |
3947 | /* Set the start of the Visual area when the Visual area was empty, we |
3948 | * were just inside quotes or the Visual area didn't start at a quote |
3949 | * and didn't include a quote. |
3950 | */ |
3951 | if (vis_empty |
3952 | || (vis_bef_curs |
3953 | && !selected_quote |
3954 | && (inside_quotes |
3955 | || (line[VIsual.col] != quotechar |
3956 | && (VIsual.col == 0 |
3957 | || line[VIsual.col - 1] != quotechar))))) { |
3958 | VIsual = curwin->w_cursor; |
3959 | redraw_curbuf_later(INVERTED); |
3960 | } |
3961 | } else { |
3962 | oap->start = curwin->w_cursor; |
3963 | oap->motion_type = kMTCharWise; |
3964 | } |
3965 | |
3966 | /* Set end position. */ |
3967 | curwin->w_cursor.col = col_end; |
3968 | if ((include || count > 1 |
3969 | /* After vi" another i" must include the ". */ |
3970 | || (!vis_empty && inside_quotes) |
3971 | ) && inc_cursor() == 2) |
3972 | inclusive = true; |
3973 | if (VIsual_active) { |
3974 | if (vis_empty || vis_bef_curs) { |
3975 | /* decrement cursor when 'selection' is not exclusive */ |
3976 | if (*p_sel != 'e') |
3977 | dec_cursor(); |
3978 | } else { |
3979 | /* Cursor is at start of Visual area. Set the end of the Visual |
3980 | * area when it was just inside quotes or it didn't end at a |
3981 | * quote. */ |
3982 | if (inside_quotes |
3983 | || (!selected_quote |
3984 | && line[VIsual.col] != quotechar |
3985 | && (line[VIsual.col] == NUL |
3986 | || line[VIsual.col + 1] != quotechar))) { |
3987 | dec_cursor(); |
3988 | VIsual = curwin->w_cursor; |
3989 | } |
3990 | curwin->w_cursor.col = col_start; |
3991 | } |
3992 | if (VIsual_mode == 'V') { |
3993 | VIsual_mode = 'v'; |
3994 | redraw_cmdline = TRUE; /* show mode later */ |
3995 | } |
3996 | } else { |
3997 | /* Set inclusive and other oap's flags. */ |
3998 | oap->inclusive = inclusive; |
3999 | } |
4000 | |
4001 | return OK; |
4002 | |
4003 | abort_search: |
4004 | if (VIsual_active && *p_sel == 'e') { |
4005 | inc_cursor(); |
4006 | if (restore_vis_bef) { |
4007 | pos_T t = curwin->w_cursor; |
4008 | |
4009 | curwin->w_cursor = VIsual; |
4010 | VIsual = t; |
4011 | } |
4012 | } |
4013 | return false; |
4014 | } |
4015 | |
4016 | |
4017 | |
4018 | /* |
4019 | * Find next search match under cursor, cursor at end. |
4020 | * Used while an operator is pending, and in Visual mode. |
4021 | */ |
4022 | int |
4023 | current_search( |
4024 | long count, |
4025 | int forward // true for forward, false for backward |
4026 | ) |
4027 | { |
4028 | bool old_p_ws = p_ws; |
4029 | pos_T save_VIsual = VIsual; |
4030 | |
4031 | /* wrapping should not occur */ |
4032 | p_ws = false; |
4033 | |
4034 | /* Correct cursor when 'selection' is exclusive */ |
4035 | if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) |
4036 | dec_cursor(); |
4037 | |
4038 | pos_T end_pos; // end position of the pattern match |
4039 | pos_T orig_pos; // position of the cursor at beginning |
4040 | pos_T pos; // position after the pattern |
4041 | int result; // result of various function calls |
4042 | |
4043 | if (VIsual_active) { |
4044 | orig_pos = pos = curwin->w_cursor; |
4045 | |
4046 | // Searching further will extend the match. |
4047 | if (forward) { |
4048 | incl(&pos); |
4049 | } else { |
4050 | decl(&pos); |
4051 | } |
4052 | } else { |
4053 | orig_pos = pos = curwin->w_cursor; |
4054 | } |
4055 | |
4056 | // Is the pattern is zero-width?, this time, don't care about the direction |
4057 | int one_char = is_one_char(spats[last_idx].pat, true, &curwin->w_cursor, |
4058 | FORWARD); |
4059 | if (one_char == -1) { |
4060 | p_ws = old_p_ws; |
4061 | return FAIL; /* pattern not found */ |
4062 | } |
4063 | |
4064 | /* |
4065 | * The trick is to first search backwards and then search forward again, |
4066 | * so that a match at the current cursor position will be correctly |
4067 | * captured. |
4068 | */ |
4069 | for (int i = 0; i < 2; i++) { |
4070 | int dir = forward ? i : !i; |
4071 | int flags = 0; |
4072 | |
4073 | if (!dir && !one_char) { |
4074 | flags = SEARCH_END; |
4075 | } |
4076 | end_pos = pos; |
4077 | |
4078 | result = searchit(curwin, curbuf, &pos, &end_pos, |
4079 | (dir ? FORWARD : BACKWARD), |
4080 | spats[last_idx].pat, i ? count : 1, |
4081 | SEARCH_KEEP | flags, RE_SEARCH, 0, NULL, NULL); |
4082 | |
4083 | // First search may fail, but then start searching from the |
4084 | // beginning of the file (cursor might be on the search match) |
4085 | // except when Visual mode is active, so that extending the visual |
4086 | // selection works. |
4087 | if (i == 1 && !result) { // not found, abort */ |
4088 | curwin->w_cursor = orig_pos; |
4089 | if (VIsual_active) |
4090 | VIsual = save_VIsual; |
4091 | p_ws = old_p_ws; |
4092 | return FAIL; |
4093 | } else if (i == 0 && !result) { |
4094 | if (forward) { // try again from start of buffer |
4095 | clearpos(&pos); |
4096 | } else { // try again from end of buffer |
4097 | // searching backwards, so set pos to last line and col |
4098 | pos.lnum = curwin->w_buffer->b_ml.ml_line_count; |
4099 | pos.col = (colnr_T)STRLEN( |
4100 | ml_get(curwin->w_buffer->b_ml.ml_line_count)); |
4101 | } |
4102 | } |
4103 | p_ws = old_p_ws; |
4104 | } |
4105 | |
4106 | pos_T start_pos = pos; |
4107 | |
4108 | p_ws = old_p_ws; |
4109 | |
4110 | if (!VIsual_active) { |
4111 | VIsual = start_pos; |
4112 | } |
4113 | |
4114 | // put cursor on last character of match |
4115 | curwin->w_cursor = end_pos; |
4116 | if (lt(VIsual, end_pos)) { |
4117 | dec_cursor(); |
4118 | } |
4119 | VIsual_active = true; |
4120 | VIsual_mode = 'v'; |
4121 | |
4122 | redraw_curbuf_later(INVERTED); // Update the inversion. |
4123 | if (*p_sel == 'e') { |
4124 | // Correction for exclusive selection depends on the direction. |
4125 | if (forward && ltoreq(VIsual, curwin->w_cursor)) { |
4126 | inc_cursor(); |
4127 | } else if (!forward && ltoreq(curwin->w_cursor, VIsual)) { |
4128 | inc(&VIsual); |
4129 | } |
4130 | } |
4131 | |
4132 | if (fdo_flags & FDO_SEARCH && KeyTyped) { |
4133 | foldOpenCursor(); |
4134 | } |
4135 | |
4136 | may_start_select('c'); |
4137 | setmouse(); |
4138 | redraw_curbuf_later(INVERTED); |
4139 | showmode(); |
4140 | |
4141 | return OK; |
4142 | } |
4143 | |
4144 | /// Check if the pattern is one character long or zero-width. |
4145 | /// If move is true, check from the beginning of the buffer, |
4146 | /// else from position "cur". |
4147 | /// "direction" is FORWARD or BACKWARD. |
4148 | /// Returns TRUE, FALSE or -1 for failure. |
4149 | static int is_one_char(char_u *pattern, bool move, pos_T *cur, |
4150 | Direction direction) |
4151 | { |
4152 | regmmatch_T regmatch; |
4153 | int nmatched = 0; |
4154 | int result = -1; |
4155 | pos_T pos; |
4156 | int save_called_emsg = called_emsg; |
4157 | int flag = 0; |
4158 | |
4159 | if (pattern == NULL) { |
4160 | pattern = spats[last_idx].pat; |
4161 | } |
4162 | |
4163 | if (search_regcomp(pattern, RE_SEARCH, RE_SEARCH, |
4164 | SEARCH_KEEP, ®match) == FAIL) |
4165 | return -1; |
4166 | |
4167 | // init startcol correctly |
4168 | regmatch.startpos[0].col = -1; |
4169 | // move to match |
4170 | if (move) { |
4171 | clearpos(&pos); |
4172 | } else { |
4173 | pos = *cur; |
4174 | // accept a match at the cursor position |
4175 | flag = SEARCH_START; |
4176 | } |
4177 | if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1, |
4178 | SEARCH_KEEP + flag, RE_SEARCH, 0, NULL, NULL) != FAIL) { |
4179 | // Zero-width pattern should match somewhere, then we can check if |
4180 | // start and end are in the same position. |
4181 | called_emsg = false; |
4182 | do { |
4183 | regmatch.startpos[0].col++; |
4184 | nmatched = vim_regexec_multi(®match, curwin, curbuf, |
4185 | pos.lnum, regmatch.startpos[0].col, |
4186 | NULL, NULL); |
4187 | if (!nmatched) { |
4188 | break; |
4189 | } |
4190 | } while (direction == FORWARD |
4191 | ? regmatch.startpos[0].col < pos.col |
4192 | : regmatch.startpos[0].col > pos.col); |
4193 | |
4194 | if (!called_emsg) { |
4195 | result = (nmatched != 0 |
4196 | && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum |
4197 | && regmatch.startpos[0].col == regmatch.endpos[0].col); |
4198 | // one char width |
4199 | if (!result && inc(&pos) >= 0 && pos.col == regmatch.endpos[0].col) { |
4200 | result = true; |
4201 | } |
4202 | } |
4203 | } |
4204 | |
4205 | called_emsg |= save_called_emsg; |
4206 | vim_regfree(regmatch.regprog); |
4207 | return result; |
4208 | } |
4209 | |
4210 | /* |
4211 | * return TRUE if line 'lnum' is empty or has white chars only. |
4212 | */ |
4213 | int linewhite(linenr_T lnum) |
4214 | { |
4215 | char_u *p; |
4216 | |
4217 | p = skipwhite(ml_get(lnum)); |
4218 | return *p == NUL; |
4219 | } |
4220 | |
4221 | // Add the search count "[3/19]" to "msgbuf". |
4222 | // When "recompute" is true Always recompute the numbers. |
4223 | static void search_stat(int dirc, pos_T *pos, |
4224 | bool show_top_bot_msg, char_u *msgbuf, bool recompute) |
4225 | { |
4226 | int save_ws = p_ws; |
4227 | int wraparound = false; |
4228 | pos_T p = (*pos); |
4229 | static pos_T lastpos = { 0, 0, 0 }; |
4230 | static int cur = 0; |
4231 | static int cnt = 0; |
4232 | static int chgtick = 0; |
4233 | static char_u *lastpat = NULL; |
4234 | static buf_T *lbuf = NULL; |
4235 | proftime_T start; |
4236 | #define OUT_OF_TIME 999 |
4237 | |
4238 | wraparound = ((dirc == '?' && lt(lastpos, p)) |
4239 | || (dirc == '/' && lt(p, lastpos))); |
4240 | |
4241 | // If anything relevant changed the count has to be recomputed. |
4242 | // STRNICMP ignores case, but we should not ignore case. |
4243 | // Unfortunately, there is no STRNICMP function. |
4244 | if (!(chgtick == buf_get_changedtick(curbuf) |
4245 | && lastpat != NULL // supress clang/NULL passed as nonnull parameter |
4246 | && STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0 |
4247 | && STRLEN(lastpat) == STRLEN(spats[last_idx].pat) |
4248 | && equalpos(lastpos, curwin->w_cursor) |
4249 | && lbuf == curbuf) |
4250 | || wraparound || cur < 0 || cur > 99 || recompute) { |
4251 | cur = 0; |
4252 | cnt = 0; |
4253 | clearpos(&lastpos); |
4254 | lbuf = curbuf; |
4255 | } |
4256 | |
4257 | if (equalpos(lastpos, curwin->w_cursor) && !wraparound |
4258 | && (dirc == '/' ? cur < cnt : cur > 0)) { |
4259 | cur += dirc == '/' ? 1 : -1; |
4260 | } else { |
4261 | p_ws = false; |
4262 | start = profile_setlimit(20L); |
4263 | while (!got_int && searchit(curwin, curbuf, &lastpos, NULL, |
4264 | FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, |
4265 | (linenr_T)0, NULL, NULL) != FAIL) { |
4266 | // Stop after passing the time limit. |
4267 | if (profile_passed_limit(start)) { |
4268 | cnt = OUT_OF_TIME; |
4269 | cur = OUT_OF_TIME; |
4270 | break; |
4271 | } |
4272 | cnt++; |
4273 | if (ltoreq(lastpos, p)) { |
4274 | cur++; |
4275 | } |
4276 | fast_breakcheck(); |
4277 | if (cnt > 99) { |
4278 | break; |
4279 | } |
4280 | } |
4281 | if (got_int) { |
4282 | cur = -1; // abort |
4283 | } |
4284 | } |
4285 | if (cur > 0) { |
4286 | char t[SEARCH_STAT_BUF_LEN] = "" ; |
4287 | int len; |
4288 | |
4289 | if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { |
4290 | if (cur == OUT_OF_TIME) { |
4291 | vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?\?/?]" ); |
4292 | } else if (cnt > 99 && cur > 99) { |
4293 | vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]" ); |
4294 | } else if (cnt > 99) { |
4295 | vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/%d]" , cur); |
4296 | } else { |
4297 | vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]" , cnt, cur); |
4298 | } |
4299 | } else { |
4300 | if (cur == OUT_OF_TIME) { |
4301 | vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]" ); |
4302 | } else if (cnt > 99 && cur > 99) { |
4303 | vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]" ); |
4304 | } else if (cnt > 99) { |
4305 | vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>99]" , cur); |
4306 | } else { |
4307 | vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]" , cur, cnt); |
4308 | } |
4309 | } |
4310 | |
4311 | len = STRLEN(t); |
4312 | if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) { |
4313 | STRCPY(t + len, " W" ); |
4314 | len += 2; |
4315 | } |
4316 | |
4317 | memmove(msgbuf + STRLEN(msgbuf) - len, t, len); |
4318 | if (dirc == '?' && cur == 100) { |
4319 | cur = -1; |
4320 | } |
4321 | |
4322 | xfree(lastpat); |
4323 | lastpat = vim_strsave(spats[last_idx].pat); |
4324 | chgtick = buf_get_changedtick(curbuf); |
4325 | lbuf = curbuf; |
4326 | lastpos = p; |
4327 | |
4328 | // keep the message even after redraw, but don't put in history |
4329 | msg_hist_off = true; |
4330 | msg_ext_set_kind("search_count" ); |
4331 | give_warning(msgbuf, false); |
4332 | msg_hist_off = false; |
4333 | } |
4334 | p_ws = save_ws; |
4335 | } |
4336 | |
4337 | /* |
4338 | * Find identifiers or defines in included files. |
4339 | * If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase. |
4340 | */ |
4341 | void |
4342 | find_pattern_in_path( |
4343 | char_u *ptr, // pointer to search pattern |
4344 | int dir, // direction of expansion |
4345 | size_t len, // length of search pattern |
4346 | bool whole, // match whole words only |
4347 | bool , // don't match inside comments |
4348 | int type, // Type of search; are we looking for a type? |
4349 | // a macro? |
4350 | long count, |
4351 | int action, // What to do when we find it |
4352 | linenr_T start_lnum, // first line to start searching |
4353 | linenr_T end_lnum // last line for searching |
4354 | ) |
4355 | { |
4356 | SearchedFile *files; /* Stack of included files */ |
4357 | SearchedFile *bigger; /* When we need more space */ |
4358 | int max_path_depth = 50; |
4359 | long match_count = 1; |
4360 | |
4361 | char_u *pat; |
4362 | char_u *new_fname; |
4363 | char_u *curr_fname = curbuf->b_fname; |
4364 | char_u *prev_fname = NULL; |
4365 | linenr_T lnum; |
4366 | int depth; |
4367 | int depth_displayed; /* For type==CHECK_PATH */ |
4368 | int old_files; |
4369 | int already_searched; |
4370 | char_u *file_line; |
4371 | char_u *line; |
4372 | char_u *p; |
4373 | char_u save_char; |
4374 | int define_matched; |
4375 | regmatch_T regmatch; |
4376 | regmatch_T incl_regmatch; |
4377 | regmatch_T def_regmatch; |
4378 | int matched = FALSE; |
4379 | int did_show = FALSE; |
4380 | int found = FALSE; |
4381 | int i; |
4382 | char_u *already = NULL; |
4383 | char_u *startp = NULL; |
4384 | char_u *inc_opt = NULL; |
4385 | win_T *curwin_save = NULL; |
4386 | const int l_g_do_tagpreview = g_do_tagpreview; |
4387 | |
4388 | regmatch.regprog = NULL; |
4389 | incl_regmatch.regprog = NULL; |
4390 | def_regmatch.regprog = NULL; |
4391 | |
4392 | file_line = xmalloc(LSIZE); |
4393 | |
4394 | if (type != CHECK_PATH && type != FIND_DEFINE |
4395 | /* when CONT_SOL is set compare "ptr" with the beginning of the line |
4396 | * is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo */ |
4397 | && !(compl_cont_status & CONT_SOL) |
4398 | ) { |
4399 | pat = xmalloc(len + 5); |
4400 | assert(len <= INT_MAX); |
4401 | sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s" , (int)len, ptr); |
4402 | /* ignore case according to p_ic, p_scs and pat */ |
4403 | regmatch.rm_ic = ignorecase(pat); |
4404 | regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0); |
4405 | xfree(pat); |
4406 | if (regmatch.regprog == NULL) |
4407 | goto fpip_end; |
4408 | } |
4409 | inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc; |
4410 | if (*inc_opt != NUL) { |
4411 | incl_regmatch.regprog = vim_regcomp(inc_opt, p_magic ? RE_MAGIC : 0); |
4412 | if (incl_regmatch.regprog == NULL) |
4413 | goto fpip_end; |
4414 | incl_regmatch.rm_ic = FALSE; /* don't ignore case in incl. pat. */ |
4415 | } |
4416 | if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL)) { |
4417 | def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL |
4418 | ? p_def : curbuf->b_p_def, p_magic ? RE_MAGIC : 0); |
4419 | if (def_regmatch.regprog == NULL) |
4420 | goto fpip_end; |
4421 | def_regmatch.rm_ic = FALSE; /* don't ignore case in define pat. */ |
4422 | } |
4423 | files = xcalloc(max_path_depth, sizeof(SearchedFile)); |
4424 | old_files = max_path_depth; |
4425 | depth = depth_displayed = -1; |
4426 | |
4427 | lnum = start_lnum; |
4428 | if (end_lnum > curbuf->b_ml.ml_line_count) |
4429 | end_lnum = curbuf->b_ml.ml_line_count; |
4430 | if (lnum > end_lnum) /* do at least one line */ |
4431 | lnum = end_lnum; |
4432 | line = ml_get(lnum); |
4433 | |
4434 | for (;; ) { |
4435 | if (incl_regmatch.regprog != NULL |
4436 | && vim_regexec(&incl_regmatch, line, (colnr_T)0)) { |
4437 | char_u *p_fname = (curr_fname == curbuf->b_fname) |
4438 | ? curbuf->b_ffname : curr_fname; |
4439 | |
4440 | if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs" ) != NULL) |
4441 | /* Use text from '\zs' to '\ze' (or end) of 'include'. */ |
4442 | new_fname = find_file_name_in_path(incl_regmatch.startp[0], |
4443 | (size_t)(incl_regmatch.endp[0] |
4444 | - incl_regmatch.startp[0]), |
4445 | FNAME_EXP|FNAME_INCL|FNAME_REL, |
4446 | 1L, p_fname); |
4447 | else |
4448 | /* Use text after match with 'include'. */ |
4449 | new_fname = file_name_in_line(incl_regmatch.endp[0], 0, |
4450 | FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL); |
4451 | already_searched = FALSE; |
4452 | if (new_fname != NULL) { |
4453 | // Check whether we have already searched in this file |
4454 | for (i = 0;; i++) { |
4455 | if (i == depth + 1) { |
4456 | i = old_files; |
4457 | } |
4458 | if (i == max_path_depth) { |
4459 | break; |
4460 | } |
4461 | if (path_full_compare(new_fname, files[i].name, true) & kEqualFiles) { |
4462 | if (type != CHECK_PATH |
4463 | && action == ACTION_SHOW_ALL && files[i].matched) { |
4464 | msg_putchar('\n'); // cursor below last one */ |
4465 | if (!got_int) { // don't display if 'q' typed at "--more--" |
4466 | // message |
4467 | msg_home_replace_hl(new_fname); |
4468 | MSG_PUTS(_(" (includes previously listed match)" )); |
4469 | prev_fname = NULL; |
4470 | } |
4471 | } |
4472 | XFREE_CLEAR(new_fname); |
4473 | already_searched = true; |
4474 | break; |
4475 | } |
4476 | } |
4477 | } |
4478 | |
4479 | if (type == CHECK_PATH && (action == ACTION_SHOW_ALL |
4480 | || (new_fname == NULL && !already_searched))) { |
4481 | if (did_show) { |
4482 | msg_putchar('\n'); // cursor below last one |
4483 | } else { |
4484 | gotocmdline(true); // cursor at status line |
4485 | MSG_PUTS_TITLE(_("--- Included files " )); |
4486 | if (action != ACTION_SHOW_ALL) { |
4487 | MSG_PUTS_TITLE(_("not found " )); |
4488 | } |
4489 | MSG_PUTS_TITLE(_("in path ---\n" )); |
4490 | } |
4491 | did_show = TRUE; |
4492 | while (depth_displayed < depth && !got_int) { |
4493 | ++depth_displayed; |
4494 | for (i = 0; i < depth_displayed; i++) |
4495 | MSG_PUTS(" " ); |
4496 | msg_home_replace(files[depth_displayed].name); |
4497 | MSG_PUTS(" -->\n" ); |
4498 | } |
4499 | if (!got_int) { /* don't display if 'q' typed |
4500 | for "--more--" message */ |
4501 | for (i = 0; i <= depth_displayed; i++) |
4502 | MSG_PUTS(" " ); |
4503 | if (new_fname != NULL) { |
4504 | /* using "new_fname" is more reliable, e.g., when |
4505 | * 'includeexpr' is set. */ |
4506 | msg_outtrans_attr(new_fname, HL_ATTR(HLF_D)); |
4507 | } else { |
4508 | /* |
4509 | * Isolate the file name. |
4510 | * Include the surrounding "" or <> if present. |
4511 | */ |
4512 | if (inc_opt != NULL |
4513 | && strstr((char *)inc_opt, "\\zs" ) != NULL) { |
4514 | /* pattern contains \zs, use the match */ |
4515 | p = incl_regmatch.startp[0]; |
4516 | i = (int)(incl_regmatch.endp[0] |
4517 | - incl_regmatch.startp[0]); |
4518 | } else { |
4519 | /* find the file name after the end of the match */ |
4520 | for (p = incl_regmatch.endp[0]; |
4521 | *p && !vim_isfilec(*p); p++) |
4522 | ; |
4523 | for (i = 0; vim_isfilec(p[i]); i++) |
4524 | ; |
4525 | } |
4526 | |
4527 | if (i == 0) { |
4528 | /* Nothing found, use the rest of the line. */ |
4529 | p = incl_regmatch.endp[0]; |
4530 | i = (int)STRLEN(p); |
4531 | } |
4532 | /* Avoid checking before the start of the line, can |
4533 | * happen if \zs appears in the regexp. */ |
4534 | else if (p > line) { |
4535 | if (p[-1] == '"' || p[-1] == '<') { |
4536 | --p; |
4537 | ++i; |
4538 | } |
4539 | if (p[i] == '"' || p[i] == '>') |
4540 | ++i; |
4541 | } |
4542 | save_char = p[i]; |
4543 | p[i] = NUL; |
4544 | msg_outtrans_attr(p, HL_ATTR(HLF_D)); |
4545 | p[i] = save_char; |
4546 | } |
4547 | |
4548 | if (new_fname == NULL && action == ACTION_SHOW_ALL) { |
4549 | if (already_searched) |
4550 | MSG_PUTS(_(" (Already listed)" )); |
4551 | else |
4552 | MSG_PUTS(_(" NOT FOUND" )); |
4553 | } |
4554 | } |
4555 | ui_flush(); /* output each line directly */ |
4556 | } |
4557 | |
4558 | if (new_fname != NULL) { |
4559 | /* Push the new file onto the file stack */ |
4560 | if (depth + 1 == old_files) { |
4561 | bigger = xmalloc(max_path_depth * 2 * sizeof(SearchedFile)); |
4562 | for (i = 0; i <= depth; i++) |
4563 | bigger[i] = files[i]; |
4564 | for (i = depth + 1; i < old_files + max_path_depth; i++) { |
4565 | bigger[i].fp = NULL; |
4566 | bigger[i].name = NULL; |
4567 | bigger[i].lnum = 0; |
4568 | bigger[i].matched = FALSE; |
4569 | } |
4570 | for (i = old_files; i < max_path_depth; i++) |
4571 | bigger[i + max_path_depth] = files[i]; |
4572 | old_files += max_path_depth; |
4573 | max_path_depth *= 2; |
4574 | xfree(files); |
4575 | files = bigger; |
4576 | } |
4577 | if ((files[depth + 1].fp = os_fopen((char *)new_fname, "r" )) == NULL) { |
4578 | xfree(new_fname); |
4579 | } else { |
4580 | if (++depth == old_files) { |
4581 | // Something wrong. We will forget one of our already visited files |
4582 | // now. |
4583 | xfree(files[old_files].name); |
4584 | ++old_files; |
4585 | } |
4586 | files[depth].name = curr_fname = new_fname; |
4587 | files[depth].lnum = 0; |
4588 | files[depth].matched = FALSE; |
4589 | if (action == ACTION_EXPAND) { |
4590 | msg_hist_off = true; // reset in msg_trunc_attr() |
4591 | vim_snprintf((char *)IObuff, IOSIZE, |
4592 | _("Scanning included file: %s" ), |
4593 | (char *)new_fname); |
4594 | msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); |
4595 | } else if (p_verbose >= 5) { |
4596 | verbose_enter(); |
4597 | smsg(_("Searching included file %s" ), |
4598 | (char *)new_fname); |
4599 | verbose_leave(); |
4600 | } |
4601 | |
4602 | } |
4603 | } |
4604 | } else { |
4605 | /* |
4606 | * Check if the line is a define (type == FIND_DEFINE) |
4607 | */ |
4608 | p = line; |
4609 | search_line: |
4610 | define_matched = FALSE; |
4611 | if (def_regmatch.regprog != NULL |
4612 | && vim_regexec(&def_regmatch, line, (colnr_T)0)) { |
4613 | /* |
4614 | * Pattern must be first identifier after 'define', so skip |
4615 | * to that position before checking for match of pattern. Also |
4616 | * don't let it match beyond the end of this identifier. |
4617 | */ |
4618 | p = def_regmatch.endp[0]; |
4619 | while (*p && !vim_iswordc(*p)) |
4620 | p++; |
4621 | define_matched = TRUE; |
4622 | } |
4623 | |
4624 | /* |
4625 | * Look for a match. Don't do this if we are looking for a |
4626 | * define and this line didn't match define_prog above. |
4627 | */ |
4628 | if (def_regmatch.regprog == NULL || define_matched) { |
4629 | if (define_matched |
4630 | || (compl_cont_status & CONT_SOL) |
4631 | ) { |
4632 | /* compare the first "len" chars from "ptr" */ |
4633 | startp = skipwhite(p); |
4634 | if (p_ic) { |
4635 | matched = !mb_strnicmp(startp, ptr, len); |
4636 | } |
4637 | else |
4638 | matched = !STRNCMP(startp, ptr, len); |
4639 | if (matched && define_matched && whole |
4640 | && vim_iswordc(startp[len])) |
4641 | matched = FALSE; |
4642 | } else if (regmatch.regprog != NULL |
4643 | && vim_regexec(®match, line, (colnr_T)(p - line))) { |
4644 | matched = TRUE; |
4645 | startp = regmatch.startp[0]; |
4646 | // Check if the line is not a comment line (unless we are |
4647 | // looking for a define). A line starting with "# define" |
4648 | // is not considered to be a comment line. |
4649 | if (skip_comments) { |
4650 | if ((*line != '#' |
4651 | || STRNCMP(skipwhite(line + 1), "define" , 6) != 0) |
4652 | && get_leader_len(line, NULL, false, true)) { |
4653 | matched = false; |
4654 | } |
4655 | |
4656 | /* |
4657 | * Also check for a "/ *" or "/ /" before the match. |
4658 | * Skips lines like "int backwards; / * normal index |
4659 | * * /" when looking for "normal". |
4660 | * Note: Doesn't skip "/ *" in comments. |
4661 | */ |
4662 | p = skipwhite(line); |
4663 | if (matched |
4664 | || (p[0] == '/' && p[1] == '*') || p[0] == '*') |
4665 | for (p = line; *p && p < startp; ++p) { |
4666 | if (matched |
4667 | && p[0] == '/' |
4668 | && (p[1] == '*' || p[1] == '/')) { |
4669 | matched = FALSE; |
4670 | /* After "//" all text is comment */ |
4671 | if (p[1] == '/') |
4672 | break; |
4673 | ++p; |
4674 | } else if (!matched && p[0] == '*' && p[1] == '/') { |
4675 | /* Can find match after "* /". */ |
4676 | matched = TRUE; |
4677 | ++p; |
4678 | } |
4679 | } |
4680 | } |
4681 | } |
4682 | } |
4683 | } |
4684 | if (matched) { |
4685 | if (action == ACTION_EXPAND) { |
4686 | bool cont_s_ipos = false; |
4687 | char_u *aux; |
4688 | |
4689 | if (depth == -1 && lnum == curwin->w_cursor.lnum) |
4690 | break; |
4691 | found = TRUE; |
4692 | aux = p = startp; |
4693 | if (compl_cont_status & CONT_ADDING) { |
4694 | p += compl_length; |
4695 | if (vim_iswordp(p)) |
4696 | goto exit_matched; |
4697 | p = find_word_start(p); |
4698 | } |
4699 | p = find_word_end(p); |
4700 | i = (int)(p - aux); |
4701 | |
4702 | if ((compl_cont_status & CONT_ADDING) && i == compl_length) { |
4703 | /* IOSIZE > compl_length, so the STRNCPY works */ |
4704 | STRNCPY(IObuff, aux, i); |
4705 | |
4706 | /* Get the next line: when "depth" < 0 from the current |
4707 | * buffer, otherwise from the included file. Jump to |
4708 | * exit_matched when past the last line. */ |
4709 | if (depth < 0) { |
4710 | if (lnum >= end_lnum) |
4711 | goto exit_matched; |
4712 | line = ml_get(++lnum); |
4713 | } else if (vim_fgets(line = file_line, |
4714 | LSIZE, files[depth].fp)) |
4715 | goto exit_matched; |
4716 | |
4717 | /* we read a line, set "already" to check this "line" later |
4718 | * if depth >= 0 we'll increase files[depth].lnum far |
4719 | * bellow -- Acevedo */ |
4720 | already = aux = p = skipwhite(line); |
4721 | p = find_word_start(p); |
4722 | p = find_word_end(p); |
4723 | if (p > aux) { |
4724 | if (*aux != ')' && IObuff[i-1] != TAB) { |
4725 | if (IObuff[i-1] != ' ') |
4726 | IObuff[i++] = ' '; |
4727 | /* IObuf =~ "\(\k\|\i\).* ", thus i >= 2*/ |
4728 | if (p_js |
4729 | && (IObuff[i-2] == '.' |
4730 | || IObuff[i-2] == '?' |
4731 | || IObuff[i-2] == '!')) { |
4732 | IObuff[i++] = ' '; |
4733 | } |
4734 | } |
4735 | /* copy as much as possible of the new word */ |
4736 | if (p - aux >= IOSIZE - i) |
4737 | p = aux + IOSIZE - i - 1; |
4738 | STRNCPY(IObuff + i, aux, p - aux); |
4739 | i += (int)(p - aux); |
4740 | cont_s_ipos = true; |
4741 | } |
4742 | IObuff[i] = NUL; |
4743 | aux = IObuff; |
4744 | |
4745 | if (i == compl_length) |
4746 | goto exit_matched; |
4747 | } |
4748 | |
4749 | const int add_r = ins_compl_add_infercase( |
4750 | aux, i, p_ic, curr_fname == curbuf->b_fname ? NULL : curr_fname, |
4751 | dir, cont_s_ipos); |
4752 | if (add_r == OK) { |
4753 | // if dir was BACKWARD then honor it just once |
4754 | dir = FORWARD; |
4755 | } else if (add_r == FAIL) { |
4756 | break; |
4757 | } |
4758 | } else if (action == ACTION_SHOW_ALL) { |
4759 | found = TRUE; |
4760 | if (!did_show) |
4761 | gotocmdline(TRUE); /* cursor at status line */ |
4762 | if (curr_fname != prev_fname) { |
4763 | if (did_show) |
4764 | msg_putchar('\n'); /* cursor below last one */ |
4765 | if (!got_int) /* don't display if 'q' typed |
4766 | at "--more--" message */ |
4767 | msg_home_replace_hl(curr_fname); |
4768 | prev_fname = curr_fname; |
4769 | } |
4770 | did_show = TRUE; |
4771 | if (!got_int) |
4772 | show_pat_in_path(line, type, TRUE, action, |
4773 | (depth == -1) ? NULL : files[depth].fp, |
4774 | (depth == -1) ? &lnum : &files[depth].lnum, |
4775 | match_count++); |
4776 | |
4777 | /* Set matched flag for this file and all the ones that |
4778 | * include it */ |
4779 | for (i = 0; i <= depth; ++i) |
4780 | files[i].matched = TRUE; |
4781 | } else if (--count <= 0) { |
4782 | found = TRUE; |
4783 | if (depth == -1 && lnum == curwin->w_cursor.lnum |
4784 | && l_g_do_tagpreview == 0 |
4785 | ) |
4786 | EMSG(_("E387: Match is on current line" )); |
4787 | else if (action == ACTION_SHOW) { |
4788 | show_pat_in_path(line, type, did_show, action, |
4789 | (depth == -1) ? NULL : files[depth].fp, |
4790 | (depth == -1) ? &lnum : &files[depth].lnum, 1L); |
4791 | did_show = TRUE; |
4792 | } else { |
4793 | /* ":psearch" uses the preview window */ |
4794 | if (l_g_do_tagpreview != 0) { |
4795 | curwin_save = curwin; |
4796 | prepare_tagpreview(true); |
4797 | } |
4798 | if (action == ACTION_SPLIT) { |
4799 | if (win_split(0, 0) == FAIL) |
4800 | break; |
4801 | RESET_BINDING(curwin); |
4802 | } |
4803 | if (depth == -1) { |
4804 | // match in current file |
4805 | if (l_g_do_tagpreview != 0) { |
4806 | if (!GETFILE_SUCCESS(getfile(curwin_save->w_buffer->b_fnum, NULL, |
4807 | NULL, true, lnum, false))) { |
4808 | break; // failed to jump to file |
4809 | } |
4810 | } else { |
4811 | setpcmark(); |
4812 | } |
4813 | curwin->w_cursor.lnum = lnum; |
4814 | check_cursor(); |
4815 | } else { |
4816 | if (!GETFILE_SUCCESS(getfile(0, files[depth].name, NULL, true, |
4817 | files[depth].lnum, false))) { |
4818 | break; // failed to jump to file |
4819 | } |
4820 | // autocommands may have changed the lnum, we don't |
4821 | // want that here |
4822 | curwin->w_cursor.lnum = files[depth].lnum; |
4823 | } |
4824 | } |
4825 | if (action != ACTION_SHOW) { |
4826 | curwin->w_cursor.col = (colnr_T)(startp - line); |
4827 | curwin->w_set_curswant = TRUE; |
4828 | } |
4829 | |
4830 | if (l_g_do_tagpreview != 0 |
4831 | && curwin != curwin_save && win_valid(curwin_save)) { |
4832 | /* Return cursor to where we were */ |
4833 | validate_cursor(); |
4834 | redraw_later(VALID); |
4835 | win_enter(curwin_save, true); |
4836 | } |
4837 | break; |
4838 | } |
4839 | exit_matched: |
4840 | matched = FALSE; |
4841 | /* look for other matches in the rest of the line if we |
4842 | * are not at the end of it already */ |
4843 | if (def_regmatch.regprog == NULL |
4844 | && action == ACTION_EXPAND |
4845 | && !(compl_cont_status & CONT_SOL) |
4846 | && *startp != NUL |
4847 | && *(p = startp + MB_PTR2LEN(startp)) != NUL) |
4848 | goto search_line; |
4849 | } |
4850 | line_breakcheck(); |
4851 | if (action == ACTION_EXPAND) |
4852 | ins_compl_check_keys(30, false); |
4853 | if (got_int || compl_interrupted) |
4854 | break; |
4855 | |
4856 | /* |
4857 | * Read the next line. When reading an included file and encountering |
4858 | * end-of-file, close the file and continue in the file that included |
4859 | * it. |
4860 | */ |
4861 | while (depth >= 0 && !already |
4862 | && vim_fgets(line = file_line, LSIZE, files[depth].fp)) { |
4863 | fclose(files[depth].fp); |
4864 | --old_files; |
4865 | files[old_files].name = files[depth].name; |
4866 | files[old_files].matched = files[depth].matched; |
4867 | --depth; |
4868 | curr_fname = (depth == -1) ? curbuf->b_fname |
4869 | : files[depth].name; |
4870 | if (depth < depth_displayed) |
4871 | depth_displayed = depth; |
4872 | } |
4873 | if (depth >= 0) { /* we could read the line */ |
4874 | files[depth].lnum++; |
4875 | /* Remove any CR and LF from the line. */ |
4876 | i = (int)STRLEN(line); |
4877 | if (i > 0 && line[i - 1] == '\n') |
4878 | line[--i] = NUL; |
4879 | if (i > 0 && line[i - 1] == '\r') |
4880 | line[--i] = NUL; |
4881 | } else if (!already) { |
4882 | if (++lnum > end_lnum) |
4883 | break; |
4884 | line = ml_get(lnum); |
4885 | } |
4886 | already = NULL; |
4887 | } |
4888 | /* End of big for (;;) loop. */ |
4889 | |
4890 | /* Close any files that are still open. */ |
4891 | for (i = 0; i <= depth; i++) { |
4892 | fclose(files[i].fp); |
4893 | xfree(files[i].name); |
4894 | } |
4895 | for (i = old_files; i < max_path_depth; i++) |
4896 | xfree(files[i].name); |
4897 | xfree(files); |
4898 | |
4899 | if (type == CHECK_PATH) { |
4900 | if (!did_show) { |
4901 | if (action != ACTION_SHOW_ALL) |
4902 | MSG(_("All included files were found" )); |
4903 | else |
4904 | MSG(_("No included files" )); |
4905 | } |
4906 | } else if (!found |
4907 | && action != ACTION_EXPAND |
4908 | ) { |
4909 | if (got_int || compl_interrupted) |
4910 | EMSG(_(e_interr)); |
4911 | else if (type == FIND_DEFINE) |
4912 | EMSG(_("E388: Couldn't find definition" )); |
4913 | else |
4914 | EMSG(_("E389: Couldn't find pattern" )); |
4915 | } |
4916 | if (action == ACTION_SHOW || action == ACTION_SHOW_ALL) |
4917 | msg_end(); |
4918 | |
4919 | fpip_end: |
4920 | xfree(file_line); |
4921 | vim_regfree(regmatch.regprog); |
4922 | vim_regfree(incl_regmatch.regprog); |
4923 | vim_regfree(def_regmatch.regprog); |
4924 | } |
4925 | |
4926 | static void show_pat_in_path(char_u *line, int type, int did_show, int action, FILE *fp, linenr_T *lnum, long count) |
4927 | { |
4928 | char_u *p; |
4929 | |
4930 | if (did_show) |
4931 | msg_putchar('\n'); /* cursor below last one */ |
4932 | else if (!msg_silent) |
4933 | gotocmdline(TRUE); /* cursor at status line */ |
4934 | if (got_int) /* 'q' typed at "--more--" message */ |
4935 | return; |
4936 | for (;; ) { |
4937 | p = line + STRLEN(line) - 1; |
4938 | if (fp != NULL) { |
4939 | /* We used fgets(), so get rid of newline at end */ |
4940 | if (p >= line && *p == '\n') |
4941 | --p; |
4942 | if (p >= line && *p == '\r') |
4943 | --p; |
4944 | *(p + 1) = NUL; |
4945 | } |
4946 | if (action == ACTION_SHOW_ALL) { |
4947 | snprintf((char *)IObuff, IOSIZE, "%3ld: " , count); // Show match nr. |
4948 | msg_puts((const char *)IObuff); |
4949 | snprintf((char *)IObuff, IOSIZE, "%4ld" , *lnum); // Show line nr. |
4950 | // Highlight line numbers. |
4951 | msg_puts_attr((const char *)IObuff, HL_ATTR(HLF_N)); |
4952 | msg_puts(" " ); |
4953 | } |
4954 | msg_prt_line(line, FALSE); |
4955 | ui_flush(); /* show one line at a time */ |
4956 | |
4957 | /* Definition continues until line that doesn't end with '\' */ |
4958 | if (got_int || type != FIND_DEFINE || p < line || *p != '\\') |
4959 | break; |
4960 | |
4961 | if (fp != NULL) { |
4962 | if (vim_fgets(line, LSIZE, fp)) /* end of file */ |
4963 | break; |
4964 | ++*lnum; |
4965 | } else { |
4966 | if (++*lnum > curbuf->b_ml.ml_line_count) |
4967 | break; |
4968 | line = ml_get(*lnum); |
4969 | } |
4970 | msg_putchar('\n'); |
4971 | } |
4972 | } |
4973 | |
4974 | /// Get last search pattern |
4975 | void get_search_pattern(SearchPattern *const pat) |
4976 | { |
4977 | memcpy(pat, &(spats[0]), sizeof(spats[0])); |
4978 | } |
4979 | |
4980 | /// Get last substitute pattern |
4981 | void get_substitute_pattern(SearchPattern *const pat) |
4982 | { |
4983 | memcpy(pat, &(spats[1]), sizeof(spats[1])); |
4984 | memset(&(pat->off), 0, sizeof(pat->off)); |
4985 | } |
4986 | |
4987 | /// Set last search pattern |
4988 | void set_search_pattern(const SearchPattern pat) |
4989 | { |
4990 | free_spat(&spats[0]); |
4991 | memcpy(&(spats[0]), &pat, sizeof(spats[0])); |
4992 | set_vv_searchforward(); |
4993 | } |
4994 | |
4995 | /// Set last substitute pattern |
4996 | void set_substitute_pattern(const SearchPattern pat) |
4997 | { |
4998 | free_spat(&spats[1]); |
4999 | memcpy(&(spats[1]), &pat, sizeof(spats[1])); |
5000 | memset(&(spats[1].off), 0, sizeof(spats[1].off)); |
5001 | } |
5002 | |
5003 | /// Set last used search pattern |
5004 | /// |
5005 | /// @param[in] is_substitute_pattern If true set substitute pattern as last |
5006 | /// used. Otherwise sets search pattern. |
5007 | void set_last_used_pattern(const bool is_substitute_pattern) |
5008 | { |
5009 | last_idx = (is_substitute_pattern ? 1 : 0); |
5010 | } |
5011 | |
5012 | /// Returns true if search pattern was the last used one |
5013 | bool search_was_last_used(void) |
5014 | { |
5015 | return last_idx == 0; |
5016 | } |
5017 | |