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 | * Code to handle tags and the tag stack |
6 | */ |
7 | |
8 | #include <assert.h> |
9 | #include <inttypes.h> |
10 | #include <stdbool.h> |
11 | #include <string.h> |
12 | |
13 | #include "nvim/vim.h" |
14 | #include "nvim/ascii.h" |
15 | #include "nvim/tag.h" |
16 | #include "nvim/buffer.h" |
17 | #include "nvim/charset.h" |
18 | #include "nvim/cursor.h" |
19 | #include "nvim/edit.h" |
20 | #include "nvim/eval.h" |
21 | #include "nvim/ex_cmds.h" |
22 | #include "nvim/ex_cmds2.h" |
23 | #include "nvim/ex_docmd.h" |
24 | #include "nvim/ex_getln.h" |
25 | #include "nvim/fileio.h" |
26 | #include "nvim/fold.h" |
27 | #include "nvim/if_cscope.h" |
28 | #include "nvim/mark.h" |
29 | #include "nvim/mbyte.h" |
30 | #include "nvim/message.h" |
31 | #include "nvim/misc1.h" |
32 | #include "nvim/file_search.h" |
33 | #include "nvim/garray.h" |
34 | #include "nvim/memory.h" |
35 | #include "nvim/move.h" |
36 | #include "nvim/option.h" |
37 | #include "nvim/os_unix.h" |
38 | #include "nvim/path.h" |
39 | #include "nvim/quickfix.h" |
40 | #include "nvim/regexp.h" |
41 | #include "nvim/screen.h" |
42 | #include "nvim/search.h" |
43 | #include "nvim/strings.h" |
44 | #include "nvim/ui.h" |
45 | #include "nvim/window.h" |
46 | #include "nvim/os/os.h" |
47 | #include "nvim/os/time.h" |
48 | #include "nvim/os/input.h" |
49 | |
50 | /* |
51 | * Structure to hold pointers to various items in a tag line. |
52 | */ |
53 | typedef struct tag_pointers { |
54 | /* filled in by parse_tag_line(): */ |
55 | char_u *tagname; /* start of tag name (skip "file:") */ |
56 | char_u *tagname_end; /* char after tag name */ |
57 | char_u *fname; /* first char of file name */ |
58 | char_u *fname_end; /* char after file name */ |
59 | char_u *command; /* first char of command */ |
60 | /* filled in by parse_match(): */ |
61 | char_u *command_end; /* first char after command */ |
62 | char_u *tag_fname; /* file name of the tags file */ |
63 | char_u *tagkind; /* "kind:" value */ |
64 | char_u *tagkind_end; /* end of tagkind */ |
65 | } tagptrs_T; |
66 | |
67 | /* |
68 | * Structure to hold info about the tag pattern being used. |
69 | */ |
70 | typedef struct { |
71 | char_u *pat; /* the pattern */ |
72 | int len; /* length of pat[] */ |
73 | char_u *head; /* start of pattern head */ |
74 | int headlen; /* length of head[] */ |
75 | regmatch_T regmatch; /* regexp program, may be NULL */ |
76 | } pat_T; |
77 | |
78 | // The matching tags are first stored in one of the hash tables. In |
79 | // which one depends on the priority of the match. |
80 | // ht_match[] is used to find duplicates, ga_match[] to keep them in sequence. |
81 | // At the end, the matches from ga_match[] are concatenated, to make a list |
82 | // sorted on priority. |
83 | #define MT_ST_CUR 0 // static match in current file |
84 | #define MT_GL_CUR 1 // global match in current file |
85 | #define MT_GL_OTH 2 // global match in other file |
86 | #define MT_ST_OTH 3 // static match in other file |
87 | #define MT_IC_OFF 4 // add for icase match |
88 | #define MT_RE_OFF 8 // add for regexp match |
89 | #define MT_MASK 7 // mask for printing priority |
90 | #define MT_COUNT 16 |
91 | |
92 | static char *mt_names[MT_COUNT/2] = |
93 | {"FSC" , "F C" , "F " , "FS " , " SC" , " C" , " " , " S " }; |
94 | |
95 | #define NOTAGFILE 99 /* return value for jumpto_tag */ |
96 | static char_u *nofile_fname = NULL; /* fname for NOTAGFILE error */ |
97 | |
98 | |
99 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
100 | # include "tag.c.generated.h" |
101 | #endif |
102 | |
103 | static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack" ); |
104 | static char_u *topmsg = (char_u *)N_("E556: at top of tag stack" ); |
105 | |
106 | static char_u *tagmatchname = NULL; /* name of last used tag */ |
107 | |
108 | /* |
109 | * Tag for preview window is remembered separately, to avoid messing up the |
110 | * normal tagstack. |
111 | */ |
112 | static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 }; |
113 | |
114 | /* |
115 | * Jump to tag; handling of tag commands and tag stack |
116 | * |
117 | * *tag != NUL: ":tag {tag}", jump to new tag, add to tag stack |
118 | * |
119 | * type == DT_TAG: ":tag [tag]", jump to newer position or same tag again |
120 | * type == DT_HELP: like DT_TAG, but don't use regexp. |
121 | * type == DT_POP: ":pop" or CTRL-T, jump to old position |
122 | * type == DT_NEXT: jump to next match of same tag |
123 | * type == DT_PREV: jump to previous match of same tag |
124 | * type == DT_FIRST: jump to first match of same tag |
125 | * type == DT_LAST: jump to last match of same tag |
126 | * type == DT_SELECT: ":tselect [tag]", select tag from a list of all matches |
127 | * type == DT_JUMP: ":tjump [tag]", jump to tag or select tag from a list |
128 | * type == DT_CSCOPE: use cscope to find the tag |
129 | * type == DT_LTAG: use location list for displaying tag matches |
130 | * type == DT_FREE: free cached matches |
131 | * |
132 | * for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise |
133 | */ |
134 | int |
135 | do_tag ( |
136 | char_u *tag, /* tag (pattern) to jump to */ |
137 | int type, |
138 | int count, |
139 | int forceit, /* :ta with ! */ |
140 | int verbose /* print "tag not found" message */ |
141 | ) |
142 | { |
143 | taggy_T *tagstack = curwin->w_tagstack; |
144 | int tagstackidx = curwin->w_tagstackidx; |
145 | int tagstacklen = curwin->w_tagstacklen; |
146 | int cur_match = 0; |
147 | int cur_fnum = curbuf->b_fnum; |
148 | int oldtagstackidx = tagstackidx; |
149 | int prevtagstackidx = tagstackidx; |
150 | int prev_num_matches; |
151 | int new_tag = FALSE; |
152 | int other_name; |
153 | int i, j, k; |
154 | int idx; |
155 | int ic; |
156 | char_u *p; |
157 | char_u *name; |
158 | int no_regexp = FALSE; |
159 | int error_cur_match = 0; |
160 | char_u *command_end; |
161 | int save_pos = FALSE; |
162 | fmark_T saved_fmark; |
163 | int taglen; |
164 | int jumped_to_tag = FALSE; |
165 | tagptrs_T tagp, tagp2; |
166 | int new_num_matches; |
167 | char_u **new_matches; |
168 | int attr; |
169 | int use_tagstack; |
170 | int skip_msg = FALSE; |
171 | char_u *buf_ffname = curbuf->b_ffname; /* name to use for |
172 | priority computation */ |
173 | |
174 | /* remember the matches for the last used tag */ |
175 | static int num_matches = 0; |
176 | static int max_num_matches = 0; /* limit used for match search */ |
177 | static char_u **matches = NULL; |
178 | static int flags; |
179 | |
180 | #ifdef EXITFREE |
181 | if (type == DT_FREE) { |
182 | /* remove the list of matches */ |
183 | FreeWild(num_matches, matches); |
184 | cs_free_tags(); |
185 | num_matches = 0; |
186 | return FALSE; |
187 | } |
188 | #endif |
189 | |
190 | if (type == DT_HELP) { |
191 | type = DT_TAG; |
192 | no_regexp = TRUE; |
193 | } |
194 | |
195 | prev_num_matches = num_matches; |
196 | free_string_option(nofile_fname); |
197 | nofile_fname = NULL; |
198 | |
199 | clearpos(&saved_fmark.mark); /* shutup gcc 4.0 */ |
200 | saved_fmark.fnum = 0; |
201 | |
202 | // Don't add a tag to the tagstack if 'tagstack' has been reset. |
203 | assert(tag != NULL); |
204 | if (!p_tgst && *tag != NUL) { // -V522 |
205 | use_tagstack = false; |
206 | new_tag = true; |
207 | if (g_do_tagpreview != 0) { |
208 | xfree(ptag_entry.tagname); |
209 | ptag_entry.tagname = vim_strsave(tag); |
210 | } |
211 | } else { |
212 | if (g_do_tagpreview != 0) |
213 | use_tagstack = FALSE; |
214 | else |
215 | use_tagstack = TRUE; |
216 | |
217 | /* new pattern, add to the tag stack */ |
218 | if (*tag != NUL |
219 | && (type == DT_TAG || type == DT_SELECT || type == DT_JUMP |
220 | || type == DT_LTAG |
221 | || type == DT_CSCOPE |
222 | )) { |
223 | if (g_do_tagpreview != 0) { |
224 | if (ptag_entry.tagname != NULL |
225 | && STRCMP(ptag_entry.tagname, tag) == 0) { |
226 | /* Jumping to same tag: keep the current match, so that |
227 | * the CursorHold autocommand example works. */ |
228 | cur_match = ptag_entry.cur_match; |
229 | cur_fnum = ptag_entry.cur_fnum; |
230 | } else { |
231 | xfree(ptag_entry.tagname); |
232 | ptag_entry.tagname = vim_strsave(tag); |
233 | } |
234 | } else { |
235 | /* |
236 | * If the last used entry is not at the top, delete all tag |
237 | * stack entries above it. |
238 | */ |
239 | while (tagstackidx < tagstacklen) |
240 | xfree(tagstack[--tagstacklen].tagname); |
241 | |
242 | /* if the tagstack is full: remove oldest entry */ |
243 | if (++tagstacklen > TAGSTACKSIZE) { |
244 | tagstacklen = TAGSTACKSIZE; |
245 | xfree(tagstack[0].tagname); |
246 | for (i = 1; i < tagstacklen; ++i) |
247 | tagstack[i - 1] = tagstack[i]; |
248 | --tagstackidx; |
249 | } |
250 | |
251 | // put the tag name in the tag stack |
252 | tagstack[tagstackidx].tagname = vim_strsave(tag); |
253 | |
254 | curwin->w_tagstacklen = tagstacklen; |
255 | |
256 | save_pos = TRUE; /* save the cursor position below */ |
257 | } |
258 | |
259 | new_tag = TRUE; |
260 | } else { |
261 | if ( |
262 | g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : |
263 | tagstacklen == 0) { |
264 | /* empty stack */ |
265 | EMSG(_(e_tagstack)); |
266 | goto end_do_tag; |
267 | } |
268 | |
269 | if (type == DT_POP) { // go to older position |
270 | const bool old_KeyTyped = KeyTyped; |
271 | if ((tagstackidx -= count) < 0) { |
272 | EMSG(_(bottommsg)); |
273 | if (tagstackidx + count == 0) { |
274 | /* We did [num]^T from the bottom of the stack */ |
275 | tagstackidx = 0; |
276 | goto end_do_tag; |
277 | } |
278 | /* We weren't at the bottom of the stack, so jump all the |
279 | * way to the bottom now. |
280 | */ |
281 | tagstackidx = 0; |
282 | } else if (tagstackidx >= tagstacklen) { /* count == 0? */ |
283 | EMSG(_(topmsg)); |
284 | goto end_do_tag; |
285 | } |
286 | |
287 | /* Make a copy of the fmark, autocommands may invalidate the |
288 | * tagstack before it's used. */ |
289 | saved_fmark = tagstack[tagstackidx].fmark; |
290 | if (saved_fmark.fnum != curbuf->b_fnum) { |
291 | /* |
292 | * Jump to other file. If this fails (e.g. because the |
293 | * file was changed) keep original position in tag stack. |
294 | */ |
295 | if (buflist_getfile(saved_fmark.fnum, saved_fmark.mark.lnum, |
296 | GETF_SETMARK, forceit) == FAIL) { |
297 | tagstackidx = oldtagstackidx; /* back to old posn */ |
298 | goto end_do_tag; |
299 | } |
300 | /* A BufReadPost autocommand may jump to the '" mark, but |
301 | * we don't what that here. */ |
302 | curwin->w_cursor.lnum = saved_fmark.mark.lnum; |
303 | } else { |
304 | setpcmark(); |
305 | curwin->w_cursor.lnum = saved_fmark.mark.lnum; |
306 | } |
307 | curwin->w_cursor.col = saved_fmark.mark.col; |
308 | curwin->w_set_curswant = TRUE; |
309 | check_cursor(); |
310 | if ((fdo_flags & FDO_TAG) && old_KeyTyped) |
311 | foldOpenCursor(); |
312 | |
313 | /* remove the old list of matches */ |
314 | FreeWild(num_matches, matches); |
315 | cs_free_tags(); |
316 | num_matches = 0; |
317 | tag_freematch(); |
318 | goto end_do_tag; |
319 | } |
320 | |
321 | if (type == DT_TAG |
322 | || type == DT_LTAG |
323 | ) { |
324 | if (g_do_tagpreview != 0) { |
325 | cur_match = ptag_entry.cur_match; |
326 | cur_fnum = ptag_entry.cur_fnum; |
327 | } else { |
328 | /* ":tag" (no argument): go to newer pattern */ |
329 | save_pos = TRUE; /* save the cursor position below */ |
330 | if ((tagstackidx += count - 1) >= tagstacklen) { |
331 | /* |
332 | * Beyond the last one, just give an error message and |
333 | * go to the last one. Don't store the cursor |
334 | * position. |
335 | */ |
336 | tagstackidx = tagstacklen - 1; |
337 | EMSG(_(topmsg)); |
338 | save_pos = FALSE; |
339 | } else if (tagstackidx < 0) { /* must have been count == 0 */ |
340 | EMSG(_(bottommsg)); |
341 | tagstackidx = 0; |
342 | goto end_do_tag; |
343 | } |
344 | cur_match = tagstack[tagstackidx].cur_match; |
345 | cur_fnum = tagstack[tagstackidx].cur_fnum; |
346 | } |
347 | new_tag = TRUE; |
348 | } else { /* go to other matching tag */ |
349 | /* Save index for when selection is cancelled. */ |
350 | prevtagstackidx = tagstackidx; |
351 | |
352 | if (g_do_tagpreview != 0) { |
353 | cur_match = ptag_entry.cur_match; |
354 | cur_fnum = ptag_entry.cur_fnum; |
355 | } else { |
356 | if (--tagstackidx < 0) |
357 | tagstackidx = 0; |
358 | cur_match = tagstack[tagstackidx].cur_match; |
359 | cur_fnum = tagstack[tagstackidx].cur_fnum; |
360 | } |
361 | switch (type) { |
362 | case DT_FIRST: cur_match = count - 1; break; |
363 | case DT_SELECT: |
364 | case DT_JUMP: |
365 | case DT_CSCOPE: |
366 | case DT_LAST: cur_match = MAXCOL - 1; break; |
367 | case DT_NEXT: cur_match += count; break; |
368 | case DT_PREV: cur_match -= count; break; |
369 | } |
370 | if (cur_match >= MAXCOL) |
371 | cur_match = MAXCOL - 1; |
372 | else if (cur_match < 0) { |
373 | EMSG(_("E425: Cannot go before first matching tag" )); |
374 | skip_msg = TRUE; |
375 | cur_match = 0; |
376 | cur_fnum = curbuf->b_fnum; |
377 | } |
378 | } |
379 | } |
380 | |
381 | if (g_do_tagpreview != 0) { |
382 | if (type != DT_SELECT && type != DT_JUMP) { |
383 | ptag_entry.cur_match = cur_match; |
384 | ptag_entry.cur_fnum = cur_fnum; |
385 | } |
386 | } else { |
387 | /* |
388 | * For ":tag [arg]" or ":tselect" remember position before the jump. |
389 | */ |
390 | saved_fmark = tagstack[tagstackidx].fmark; |
391 | if (save_pos) { |
392 | tagstack[tagstackidx].fmark.mark = curwin->w_cursor; |
393 | tagstack[tagstackidx].fmark.fnum = curbuf->b_fnum; |
394 | } |
395 | |
396 | /* Curwin will change in the call to jumpto_tag() if ":stag" was |
397 | * used or an autocommand jumps to another window; store value of |
398 | * tagstackidx now. */ |
399 | curwin->w_tagstackidx = tagstackidx; |
400 | if (type != DT_SELECT && type != DT_JUMP) { |
401 | curwin->w_tagstack[tagstackidx].cur_match = cur_match; |
402 | curwin->w_tagstack[tagstackidx].cur_fnum = cur_fnum; |
403 | } |
404 | } |
405 | } |
406 | |
407 | /* When not using the current buffer get the name of buffer "cur_fnum". |
408 | * Makes sure that the tag order doesn't change when using a remembered |
409 | * position for "cur_match". */ |
410 | if (cur_fnum != curbuf->b_fnum) { |
411 | buf_T *buf = buflist_findnr(cur_fnum); |
412 | |
413 | if (buf != NULL) |
414 | buf_ffname = buf->b_ffname; |
415 | } |
416 | |
417 | /* |
418 | * Repeat searching for tags, when a file has not been found. |
419 | */ |
420 | for (;; ) { |
421 | /* |
422 | * When desired match not found yet, try to find it (and others). |
423 | */ |
424 | if (use_tagstack) |
425 | name = tagstack[tagstackidx].tagname; |
426 | else if (g_do_tagpreview != 0) |
427 | name = ptag_entry.tagname; |
428 | else |
429 | name = tag; |
430 | other_name = (tagmatchname == NULL || STRCMP(tagmatchname, name) != 0); |
431 | if (new_tag |
432 | || (cur_match >= num_matches && max_num_matches != MAXCOL) |
433 | || other_name) { |
434 | if (other_name) { |
435 | xfree(tagmatchname); |
436 | tagmatchname = vim_strsave(name); |
437 | } |
438 | |
439 | if (type == DT_SELECT || type == DT_JUMP |
440 | || type == DT_LTAG) { |
441 | cur_match = MAXCOL - 1; |
442 | } |
443 | if (type == DT_TAG) { |
444 | max_num_matches = MAXCOL; |
445 | } else { |
446 | max_num_matches = cur_match + 1; |
447 | } |
448 | |
449 | /* when the argument starts with '/', use it as a regexp */ |
450 | if (!no_regexp && *name == '/') { |
451 | flags = TAG_REGEXP; |
452 | ++name; |
453 | } else |
454 | flags = TAG_NOIC; |
455 | |
456 | if (type == DT_CSCOPE) |
457 | flags = TAG_CSCOPE; |
458 | if (verbose) |
459 | flags |= TAG_VERBOSE; |
460 | if (find_tags(name, &new_num_matches, &new_matches, flags, |
461 | max_num_matches, buf_ffname) == OK |
462 | && new_num_matches < max_num_matches) |
463 | max_num_matches = MAXCOL; /* If less than max_num_matches |
464 | found: all matches found. */ |
465 | |
466 | /* If there already were some matches for the same name, move them |
467 | * to the start. Avoids that the order changes when using |
468 | * ":tnext" and jumping to another file. */ |
469 | if (!new_tag && !other_name) { |
470 | /* Find the position of each old match in the new list. Need |
471 | * to use parse_match() to find the tag line. */ |
472 | idx = 0; |
473 | for (j = 0; j < num_matches; ++j) { |
474 | parse_match(matches[j], &tagp); |
475 | for (i = idx; i < new_num_matches; ++i) { |
476 | parse_match(new_matches[i], &tagp2); |
477 | if (STRCMP(tagp.tagname, tagp2.tagname) == 0) { |
478 | p = new_matches[i]; |
479 | for (k = i; k > idx; --k) |
480 | new_matches[k] = new_matches[k - 1]; |
481 | new_matches[idx++] = p; |
482 | break; |
483 | } |
484 | } |
485 | } |
486 | } |
487 | FreeWild(num_matches, matches); |
488 | num_matches = new_num_matches; |
489 | matches = new_matches; |
490 | } |
491 | |
492 | if (num_matches <= 0) { |
493 | if (verbose) |
494 | EMSG2(_("E426: tag not found: %s" ), name); |
495 | g_do_tagpreview = 0; |
496 | } else { |
497 | bool ask_for_selection = false; |
498 | |
499 | if (type == DT_CSCOPE && num_matches > 1) { |
500 | cs_print_tags(); |
501 | ask_for_selection = true; |
502 | } else if (type == DT_TAG && *tag != NUL) { |
503 | // If a count is supplied to the ":tag <name>" command, then |
504 | // jump to count'th matching tag. |
505 | cur_match = count > 0 ? count - 1 : 0; |
506 | } else if (type == DT_SELECT || (type == DT_JUMP && num_matches > 1)) { |
507 | // List all the matching tags. |
508 | // Assume that the first match indicates how long the tags can |
509 | // be, and align the file names to that. |
510 | parse_match(matches[0], &tagp); |
511 | taglen = (int)(tagp.tagname_end - tagp.tagname + 2); |
512 | if (taglen < 18) |
513 | taglen = 18; |
514 | if (taglen > Columns - 25) |
515 | taglen = MAXCOL; |
516 | if (msg_col == 0) |
517 | msg_didout = FALSE; /* overwrite previous message */ |
518 | msg_start(); |
519 | MSG_PUTS_ATTR(_(" # pri kind tag" ), HL_ATTR(HLF_T)); |
520 | msg_clr_eos(); |
521 | taglen_advance(taglen); |
522 | MSG_PUTS_ATTR(_("file\n" ), HL_ATTR(HLF_T)); |
523 | |
524 | for (i = 0; i < num_matches && !got_int; i++) { |
525 | parse_match(matches[i], &tagp); |
526 | if (!new_tag && ((g_do_tagpreview != 0 && i == ptag_entry.cur_match) |
527 | || (use_tagstack |
528 | && i == tagstack[tagstackidx].cur_match))) { |
529 | *IObuff = '>'; |
530 | } else { |
531 | *IObuff = ' '; |
532 | } |
533 | vim_snprintf((char *)IObuff + 1, IOSIZE - 1, "%2d %s " , i + 1, |
534 | mt_names[matches[i][0] & MT_MASK]); |
535 | msg_puts((const char *)IObuff); |
536 | if (tagp.tagkind != NULL) { |
537 | msg_outtrans_len(tagp.tagkind, |
538 | (int)(tagp.tagkind_end - tagp.tagkind)); |
539 | } |
540 | msg_advance(13); |
541 | msg_outtrans_len_attr(tagp.tagname, |
542 | (int)(tagp.tagname_end - tagp.tagname), |
543 | HL_ATTR(HLF_T)); |
544 | msg_putchar(' '); |
545 | taglen_advance(taglen); |
546 | |
547 | /* Find out the actual file name. If it is long, truncate |
548 | * it and put "..." in the middle */ |
549 | p = tag_full_fname(&tagp); |
550 | msg_puts_long_attr(p, HL_ATTR(HLF_D)); |
551 | xfree(p); |
552 | |
553 | if (msg_col > 0) |
554 | msg_putchar('\n'); |
555 | if (got_int) |
556 | break; |
557 | msg_advance(15); |
558 | |
559 | /* print any extra fields */ |
560 | command_end = tagp.command_end; |
561 | if (command_end != NULL) { |
562 | p = command_end + 3; |
563 | while (*p && *p != '\r' && *p != '\n') { |
564 | while (*p == TAB) |
565 | ++p; |
566 | |
567 | /* skip "file:" without a value (static tag) */ |
568 | if (STRNCMP(p, "file:" , 5) == 0 |
569 | && ascii_isspace(p[5])) { |
570 | p += 5; |
571 | continue; |
572 | } |
573 | /* skip "kind:<kind>" and "<kind>" */ |
574 | if (p == tagp.tagkind |
575 | || (p + 5 == tagp.tagkind |
576 | && STRNCMP(p, "kind:" , 5) == 0)) { |
577 | p = tagp.tagkind_end; |
578 | continue; |
579 | } |
580 | // print all other extra fields |
581 | attr = HL_ATTR(HLF_CM); |
582 | while (*p && *p != '\r' && *p != '\n') { |
583 | if (msg_col + ptr2cells(p) >= Columns) { |
584 | msg_putchar('\n'); |
585 | if (got_int) |
586 | break; |
587 | msg_advance(15); |
588 | } |
589 | p = msg_outtrans_one(p, attr); |
590 | if (*p == TAB) { |
591 | msg_puts_attr(" " , attr); |
592 | break; |
593 | } |
594 | if (*p == ':') |
595 | attr = 0; |
596 | } |
597 | } |
598 | if (msg_col > 15) { |
599 | msg_putchar('\n'); |
600 | if (got_int) |
601 | break; |
602 | msg_advance(15); |
603 | } |
604 | } else { |
605 | for (p = tagp.command; |
606 | *p && *p != '\r' && *p != '\n'; ++p) |
607 | ; |
608 | command_end = p; |
609 | } |
610 | |
611 | /* |
612 | * Put the info (in several lines) at column 15. |
613 | * Don't display "/^" and "?^". |
614 | */ |
615 | p = tagp.command; |
616 | if (*p == '/' || *p == '?') { |
617 | ++p; |
618 | if (*p == '^') |
619 | ++p; |
620 | } |
621 | /* Remove leading whitespace from pattern */ |
622 | while (p != command_end && ascii_isspace(*p)) |
623 | ++p; |
624 | |
625 | while (p != command_end) { |
626 | if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) |
627 | msg_putchar('\n'); |
628 | if (got_int) |
629 | break; |
630 | msg_advance(15); |
631 | |
632 | // Skip backslash used for escaping a command char or a backslash. |
633 | if (*p == '\\' && (*(p + 1) == *tagp.command |
634 | || *(p + 1) == '\\')) { |
635 | ++p; |
636 | } |
637 | |
638 | if (*p == TAB) { |
639 | msg_putchar(' '); |
640 | ++p; |
641 | } else |
642 | p = msg_outtrans_one(p, 0); |
643 | |
644 | /* don't display the "$/;\"" and "$?;\"" */ |
645 | if (p == command_end - 2 && *p == '$' |
646 | && *(p + 1) == *tagp.command) |
647 | break; |
648 | /* don't display matching '/' or '?' */ |
649 | if (p == command_end - 1 && *p == *tagp.command |
650 | && (*p == '/' || *p == '?')) |
651 | break; |
652 | } |
653 | if (msg_col) |
654 | msg_putchar('\n'); |
655 | os_breakcheck(); |
656 | } |
657 | if (got_int) { |
658 | got_int = false; // only stop the listing |
659 | } |
660 | ask_for_selection = true; |
661 | } else if (type == DT_LTAG) { |
662 | list_T *list; |
663 | char_u tag_name[128 + 1]; |
664 | char_u *fname; |
665 | char_u *cmd; |
666 | |
667 | /* |
668 | * Add the matching tags to the location list for the current |
669 | * window. |
670 | */ |
671 | |
672 | fname = xmalloc(MAXPATHL + 1); |
673 | cmd = xmalloc(CMDBUFFSIZE + 1); |
674 | list = tv_list_alloc(num_matches); |
675 | |
676 | for (i = 0; i < num_matches; ++i) { |
677 | int len, cmd_len; |
678 | long lnum; |
679 | dict_T *dict; |
680 | |
681 | parse_match(matches[i], &tagp); |
682 | |
683 | /* Save the tag name */ |
684 | len = (int)(tagp.tagname_end - tagp.tagname); |
685 | if (len > 128) |
686 | len = 128; |
687 | STRLCPY(tag_name, tagp.tagname, len + 1); |
688 | |
689 | /* Save the tag file name */ |
690 | p = tag_full_fname(&tagp); |
691 | STRLCPY(fname, p, MAXPATHL + 1); |
692 | xfree(p); |
693 | |
694 | /* |
695 | * Get the line number or the search pattern used to locate |
696 | * the tag. |
697 | */ |
698 | lnum = 0; |
699 | if (isdigit(*tagp.command)) |
700 | /* Line number is used to locate the tag */ |
701 | lnum = atol((char *)tagp.command); |
702 | else { |
703 | char_u *cmd_start, *cmd_end; |
704 | |
705 | /* Search pattern is used to locate the tag */ |
706 | |
707 | /* Locate the end of the command */ |
708 | cmd_start = tagp.command; |
709 | cmd_end = tagp.command_end; |
710 | if (cmd_end == NULL) { |
711 | for (p = tagp.command; |
712 | *p && *p != '\r' && *p != '\n'; ++p) |
713 | ; |
714 | cmd_end = p; |
715 | } |
716 | |
717 | /* |
718 | * Now, cmd_end points to the character after the |
719 | * command. Adjust it to point to the last |
720 | * character of the command. |
721 | */ |
722 | cmd_end--; |
723 | |
724 | /* |
725 | * Skip the '/' and '?' characters at the |
726 | * beginning and end of the search pattern. |
727 | */ |
728 | if (*cmd_start == '/' || *cmd_start == '?') |
729 | cmd_start++; |
730 | |
731 | if (*cmd_end == '/' || *cmd_end == '?') |
732 | cmd_end--; |
733 | |
734 | len = 0; |
735 | cmd[0] = NUL; |
736 | |
737 | /* |
738 | * If "^" is present in the tag search pattern, then |
739 | * copy it first. |
740 | */ |
741 | if (*cmd_start == '^') { |
742 | STRCPY(cmd, "^" ); |
743 | cmd_start++; |
744 | len++; |
745 | } |
746 | |
747 | /* |
748 | * Precede the tag pattern with \V to make it very |
749 | * nomagic. |
750 | */ |
751 | STRCAT(cmd, "\\V" ); |
752 | len += 2; |
753 | |
754 | cmd_len = (int)(cmd_end - cmd_start + 1); |
755 | if (cmd_len > (CMDBUFFSIZE - 5)) |
756 | cmd_len = CMDBUFFSIZE - 5; |
757 | STRNCAT(cmd, cmd_start, cmd_len); |
758 | len += cmd_len; |
759 | |
760 | if (cmd[len - 1] == '$') { |
761 | /* |
762 | * Replace '$' at the end of the search pattern |
763 | * with '\$' |
764 | */ |
765 | cmd[len - 1] = '\\'; |
766 | cmd[len] = '$'; |
767 | len++; |
768 | } |
769 | |
770 | cmd[len] = NUL; |
771 | } |
772 | |
773 | dict = tv_dict_alloc(); |
774 | tv_list_append_dict(list, dict); |
775 | |
776 | tv_dict_add_str(dict, S_LEN("text" ), (const char *)tag_name); |
777 | tv_dict_add_str(dict, S_LEN("filename" ), (const char *)fname); |
778 | tv_dict_add_nr(dict, S_LEN("lnum" ), lnum); |
779 | if (lnum == 0) { |
780 | tv_dict_add_str(dict, S_LEN("pattern" ), (const char *)cmd); |
781 | } |
782 | } |
783 | |
784 | vim_snprintf((char *)IObuff, IOSIZE, "ltag %s" , tag); |
785 | set_errorlist(curwin, list, ' ', IObuff, NULL); |
786 | |
787 | tv_list_free(list); |
788 | xfree(fname); |
789 | xfree(cmd); |
790 | |
791 | cur_match = 0; /* Jump to the first tag */ |
792 | } |
793 | |
794 | if (ask_for_selection) { |
795 | // Ask to select a tag from the list. |
796 | i = prompt_for_number(NULL); |
797 | if (i <= 0 || i > num_matches || got_int) { |
798 | /* no valid choice: don't change anything */ |
799 | if (use_tagstack) { |
800 | tagstack[tagstackidx].fmark = saved_fmark; |
801 | tagstackidx = prevtagstackidx; |
802 | } |
803 | cs_free_tags(); |
804 | jumped_to_tag = TRUE; |
805 | break; |
806 | } |
807 | cur_match = i - 1; |
808 | } |
809 | |
810 | if (cur_match >= num_matches) { |
811 | /* Avoid giving this error when a file wasn't found and we're |
812 | * looking for a match in another file, which wasn't found. |
813 | * There will be an EMSG("file doesn't exist") below then. */ |
814 | if ((type == DT_NEXT || type == DT_FIRST) |
815 | && nofile_fname == NULL) { |
816 | if (num_matches == 1) |
817 | EMSG(_("E427: There is only one matching tag" )); |
818 | else |
819 | EMSG(_("E428: Cannot go beyond last matching tag" )); |
820 | skip_msg = TRUE; |
821 | } |
822 | cur_match = num_matches - 1; |
823 | } |
824 | if (use_tagstack) { |
825 | tagstack[tagstackidx].cur_match = cur_match; |
826 | tagstack[tagstackidx].cur_fnum = cur_fnum; |
827 | ++tagstackidx; |
828 | } else if (g_do_tagpreview != 0) { |
829 | ptag_entry.cur_match = cur_match; |
830 | ptag_entry.cur_fnum = cur_fnum; |
831 | } |
832 | |
833 | /* |
834 | * Only when going to try the next match, report that the previous |
835 | * file didn't exist. Otherwise an EMSG() is given below. |
836 | */ |
837 | if (nofile_fname != NULL && error_cur_match != cur_match) |
838 | smsg(_("File \"%s\" does not exist" ), nofile_fname); |
839 | |
840 | |
841 | ic = (matches[cur_match][0] & MT_IC_OFF); |
842 | if (type != DT_TAG && type != DT_SELECT && type != DT_JUMP |
843 | && type != DT_CSCOPE |
844 | && (num_matches > 1 || ic) |
845 | && !skip_msg) { |
846 | /* Give an indication of the number of matching tags */ |
847 | sprintf((char *)IObuff, _("tag %d of %d%s" ), |
848 | cur_match + 1, |
849 | num_matches, |
850 | max_num_matches != MAXCOL ? _(" or more" ) : "" ); |
851 | if (ic) |
852 | STRCAT(IObuff, _(" Using tag with different case!" )); |
853 | if ((num_matches > prev_num_matches || new_tag) |
854 | && num_matches > 1) { |
855 | if (ic) { |
856 | msg_attr((const char *)IObuff, HL_ATTR(HLF_W)); |
857 | } else { |
858 | msg(IObuff); |
859 | } |
860 | msg_scroll = true; // Don't overwrite this message. |
861 | } else { |
862 | give_warning(IObuff, ic); |
863 | } |
864 | if (ic && !msg_scrolled && msg_silent == 0) { |
865 | ui_flush(); |
866 | os_delay(1000L, true); |
867 | } |
868 | } |
869 | |
870 | /* Let the SwapExists event know what tag we are jumping to. */ |
871 | vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r" , name); |
872 | set_vim_var_string(VV_SWAPCOMMAND, (char *) IObuff, -1); |
873 | |
874 | /* |
875 | * Jump to the desired match. |
876 | */ |
877 | i = jumpto_tag(matches[cur_match], forceit, type != DT_CSCOPE); |
878 | |
879 | set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); |
880 | |
881 | if (i == NOTAGFILE) { |
882 | /* File not found: try again with another matching tag */ |
883 | if ((type == DT_PREV && cur_match > 0) |
884 | || ((type == DT_TAG || type == DT_NEXT |
885 | || type == DT_FIRST) |
886 | && (max_num_matches != MAXCOL |
887 | || cur_match < num_matches - 1))) { |
888 | error_cur_match = cur_match; |
889 | if (use_tagstack) |
890 | --tagstackidx; |
891 | if (type == DT_PREV) |
892 | --cur_match; |
893 | else { |
894 | type = DT_NEXT; |
895 | ++cur_match; |
896 | } |
897 | continue; |
898 | } |
899 | EMSG2(_("E429: File \"%s\" does not exist" ), nofile_fname); |
900 | } else { |
901 | /* We may have jumped to another window, check that |
902 | * tagstackidx is still valid. */ |
903 | if (use_tagstack && tagstackidx > curwin->w_tagstacklen) |
904 | tagstackidx = curwin->w_tagstackidx; |
905 | jumped_to_tag = TRUE; |
906 | } |
907 | } |
908 | break; |
909 | } |
910 | |
911 | end_do_tag: |
912 | /* Only store the new index when using the tagstack and it's valid. */ |
913 | if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) |
914 | curwin->w_tagstackidx = tagstackidx; |
915 | postponed_split = 0; // don't split next time |
916 | g_do_tagpreview = 0; // don't do tag preview next time |
917 | |
918 | return jumped_to_tag; |
919 | } |
920 | |
921 | /* |
922 | * Free cached tags. |
923 | */ |
924 | void tag_freematch(void) |
925 | { |
926 | XFREE_CLEAR(tagmatchname); |
927 | } |
928 | |
929 | static void taglen_advance(int l) |
930 | { |
931 | if (l == MAXCOL) { |
932 | msg_putchar('\n'); |
933 | msg_advance(24); |
934 | } else |
935 | msg_advance(13 + l); |
936 | } |
937 | |
938 | /* |
939 | * Print the tag stack |
940 | */ |
941 | void do_tags(exarg_T *eap) |
942 | { |
943 | int i; |
944 | char_u *name; |
945 | taggy_T *tagstack = curwin->w_tagstack; |
946 | int tagstackidx = curwin->w_tagstackidx; |
947 | int tagstacklen = curwin->w_tagstacklen; |
948 | |
949 | /* Highlight title */ |
950 | MSG_PUTS_TITLE(_("\n # TO tag FROM line in file/text" )); |
951 | for (i = 0; i < tagstacklen; ++i) { |
952 | if (tagstack[i].tagname != NULL) { |
953 | name = fm_getname(&(tagstack[i].fmark), 30); |
954 | if (name == NULL) /* file name not available */ |
955 | continue; |
956 | |
957 | msg_putchar('\n'); |
958 | vim_snprintf((char *)IObuff, IOSIZE, "%c%2d %2d %-15s %5ld " , |
959 | i == tagstackidx ? '>' : ' ', |
960 | i + 1, |
961 | tagstack[i].cur_match + 1, |
962 | tagstack[i].tagname, |
963 | tagstack[i].fmark.mark.lnum); |
964 | msg_outtrans(IObuff); |
965 | msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum |
966 | ? HL_ATTR(HLF_D) : 0); |
967 | xfree(name); |
968 | } |
969 | ui_flush(); /* show one line at a time */ |
970 | } |
971 | if (tagstackidx == tagstacklen) /* idx at top of stack */ |
972 | MSG_PUTS("\n>" ); |
973 | } |
974 | |
975 | |
976 | |
977 | /* |
978 | * Compare two strings, for length "len", ignoring case the ASCII way. |
979 | * return 0 for match, < 0 for smaller, > 0 for bigger |
980 | * Make sure case is folded to uppercase in comparison (like for 'sort -f') |
981 | */ |
982 | static int tag_strnicmp(char_u *s1, char_u *s2, size_t len) |
983 | { |
984 | int i; |
985 | |
986 | while (len > 0) { |
987 | i = TOUPPER_ASC(*s1) - TOUPPER_ASC(*s2); |
988 | if (i != 0) |
989 | return i; /* this character different */ |
990 | if (*s1 == NUL) |
991 | break; /* strings match until NUL */ |
992 | ++s1; |
993 | ++s2; |
994 | --len; |
995 | } |
996 | return 0; /* strings match */ |
997 | } |
998 | |
999 | |
1000 | /* |
1001 | * Extract info from the tag search pattern "pats->pat". |
1002 | */ |
1003 | static void prepare_pats(pat_T *pats, int has_re) |
1004 | { |
1005 | pats->head = pats->pat; |
1006 | pats->headlen = pats->len; |
1007 | if (has_re) { |
1008 | /* When the pattern starts with '^' or "\\<", binary searching can be |
1009 | * used (much faster). */ |
1010 | if (pats->pat[0] == '^') |
1011 | pats->head = pats->pat + 1; |
1012 | else if (pats->pat[0] == '\\' && pats->pat[1] == '<') |
1013 | pats->head = pats->pat + 2; |
1014 | if (pats->head == pats->pat) |
1015 | pats->headlen = 0; |
1016 | else |
1017 | for (pats->headlen = 0; pats->head[pats->headlen] != NUL; |
1018 | ++pats->headlen) |
1019 | if (vim_strchr((char_u *)(p_magic ? ".[~*\\$" : "\\$" ), |
1020 | pats->head[pats->headlen]) != NULL) |
1021 | break; |
1022 | if (p_tl != 0 && pats->headlen > p_tl) /* adjust for 'taglength' */ |
1023 | pats->headlen = p_tl; |
1024 | } |
1025 | |
1026 | if (has_re) |
1027 | pats->regmatch.regprog = vim_regcomp(pats->pat, p_magic ? RE_MAGIC : 0); |
1028 | else |
1029 | pats->regmatch.regprog = NULL; |
1030 | } |
1031 | |
1032 | /* |
1033 | * find_tags() - search for tags in tags files |
1034 | * |
1035 | * Return FAIL if search completely failed (*num_matches will be 0, *matchesp |
1036 | * will be NULL), OK otherwise. |
1037 | * |
1038 | * There is a priority in which type of tag is recognized. |
1039 | * |
1040 | * 6. A static or global tag with a full matching tag for the current file. |
1041 | * 5. A global tag with a full matching tag for another file. |
1042 | * 4. A static tag with a full matching tag for another file. |
1043 | * 3. A static or global tag with an ignore-case matching tag for the |
1044 | * current file. |
1045 | * 2. A global tag with an ignore-case matching tag for another file. |
1046 | * 1. A static tag with an ignore-case matching tag for another file. |
1047 | * |
1048 | * Tags in an emacs-style tags file are always global. |
1049 | * |
1050 | * flags: |
1051 | * TAG_HELP only search for help tags |
1052 | * TAG_NAMES only return name of tag |
1053 | * TAG_REGEXP use "pat" as a regexp |
1054 | * TAG_NOIC don't always ignore case |
1055 | * TAG_KEEP_LANG keep language |
1056 | * TAG_CSCOPE use cscope results for tags |
1057 | */ |
1058 | int |
1059 | find_tags ( |
1060 | char_u *pat, /* pattern to search for */ |
1061 | int *num_matches, /* return: number of matches found */ |
1062 | char_u ***matchesp, /* return: array of matches found */ |
1063 | int flags, |
1064 | int mincount, /* MAXCOL: find all matches |
1065 | other: minimal number of matches */ |
1066 | char_u *buf_ffname /* name of buffer for priority */ |
1067 | ) |
1068 | { |
1069 | FILE *fp; |
1070 | char_u *lbuf; /* line buffer */ |
1071 | int lbuf_size = LSIZE; /* length of lbuf */ |
1072 | char_u *tag_fname; /* name of tag file */ |
1073 | tagname_T tn; /* info for get_tagfname() */ |
1074 | int first_file; /* trying first tag file */ |
1075 | tagptrs_T tagp; |
1076 | int did_open = FALSE; /* did open a tag file */ |
1077 | int stop_searching = FALSE; /* stop when match found or error */ |
1078 | int retval = FAIL; /* return value */ |
1079 | int is_static; /* current tag line is static */ |
1080 | int is_current; /* file name matches */ |
1081 | int eof = FALSE; /* found end-of-file */ |
1082 | char_u *p; |
1083 | char_u *s; |
1084 | int i; |
1085 | int tag_file_sorted = NUL; // !_TAG_FILE_SORTED value |
1086 | struct tag_search_info { // Binary search file offsets |
1087 | off_T low_offset; // offset for first char of first line that |
1088 | // could match |
1089 | off_T high_offset; // offset of char after last line that could |
1090 | // match |
1091 | off_T curr_offset; // Current file offset in search range |
1092 | off_T curr_offset_used; // curr_offset used when skipping back |
1093 | off_T match_offset; // Where the binary search found a tag |
1094 | int low_char; // first char at low_offset |
1095 | int high_char; // first char at high_offset |
1096 | } search_info; |
1097 | off_T filesize; |
1098 | int tagcmp; |
1099 | off_T offset; |
1100 | int round; |
1101 | enum { |
1102 | TS_START, /* at start of file */ |
1103 | TS_LINEAR /* linear searching forward, till EOF */ |
1104 | , TS_BINARY, /* binary searching */ |
1105 | TS_SKIP_BACK, /* skipping backwards */ |
1106 | TS_STEP_FORWARD /* stepping forwards */ |
1107 | } state; /* Current search state */ |
1108 | |
1109 | int cmplen; |
1110 | int match; /* matches */ |
1111 | int match_no_ic = 0; /* matches with rm_ic == FALSE */ |
1112 | int match_re; /* match with regexp */ |
1113 | int matchoff = 0; |
1114 | int save_emsg_off; |
1115 | |
1116 | |
1117 | char_u *mfp; |
1118 | garray_T ga_match[MT_COUNT]; // stores matches in sequence |
1119 | hashtab_T ht_match[MT_COUNT]; // stores matches by key |
1120 | hash_T hash = 0; |
1121 | int match_count = 0; // number of matches found |
1122 | char_u **matches; |
1123 | int mtt; |
1124 | int help_save; |
1125 | int help_pri = 0; |
1126 | char_u *help_lang_find = NULL; // lang to be found |
1127 | char_u help_lang[3]; // lang of current tags file |
1128 | char_u *saved_pat = NULL; // copy of pat[] |
1129 | bool is_txt = false; |
1130 | |
1131 | pat_T orgpat; /* holds unconverted pattern info */ |
1132 | vimconv_T vimconv; |
1133 | |
1134 | int findall = (mincount == MAXCOL || mincount == TAG_MANY); |
1135 | /* find all matching tags */ |
1136 | int sort_error = FALSE; /* tags file not sorted */ |
1137 | int linear; /* do a linear search */ |
1138 | int sortic = FALSE; /* tag file sorted in nocase */ |
1139 | int line_error = FALSE; /* syntax error */ |
1140 | int has_re = (flags & TAG_REGEXP); /* regexp used */ |
1141 | int help_only = (flags & TAG_HELP); |
1142 | int name_only = (flags & TAG_NAMES); |
1143 | int noic = (flags & TAG_NOIC); |
1144 | int get_it_again = FALSE; |
1145 | int use_cscope = (flags & TAG_CSCOPE); |
1146 | int verbose = (flags & TAG_VERBOSE); |
1147 | int save_p_ic = p_ic; |
1148 | |
1149 | // Change the value of 'ignorecase' according to 'tagcase' for the |
1150 | // duration of this function. |
1151 | switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags) { |
1152 | case TC_FOLLOWIC: |
1153 | break; |
1154 | case TC_IGNORE: |
1155 | p_ic = true; |
1156 | break; |
1157 | case TC_MATCH: |
1158 | p_ic = false; |
1159 | break; |
1160 | case TC_FOLLOWSCS: |
1161 | p_ic = ignorecase(pat); |
1162 | break; |
1163 | case TC_SMART: |
1164 | p_ic = ignorecase_opt(pat, true, true); |
1165 | break; |
1166 | default: |
1167 | assert(false); |
1168 | } |
1169 | |
1170 | help_save = curbuf->b_help; |
1171 | orgpat.pat = pat; |
1172 | vimconv.vc_type = CONV_NONE; |
1173 | |
1174 | /* |
1175 | * Allocate memory for the buffers that are used |
1176 | */ |
1177 | lbuf = xmalloc(lbuf_size); |
1178 | tag_fname = xmalloc(MAXPATHL + 1); |
1179 | for (mtt = 0; mtt < MT_COUNT; mtt++) { |
1180 | ga_init(&ga_match[mtt], sizeof(char_u *), 100); |
1181 | hash_init(&ht_match[mtt]); |
1182 | } |
1183 | |
1184 | STRCPY(tag_fname, "from cscope" ); /* for error messages */ |
1185 | |
1186 | /* |
1187 | * Initialize a few variables |
1188 | */ |
1189 | if (help_only) { // want tags from help file |
1190 | curbuf->b_help = true; // will be restored later |
1191 | } else if (use_cscope) { |
1192 | // Make sure we don't mix help and cscope, confuses Coverity. |
1193 | help_only = false; |
1194 | curbuf->b_help = false; |
1195 | } |
1196 | |
1197 | orgpat.len = (int)STRLEN(pat); |
1198 | if (curbuf->b_help) { |
1199 | /* When "@ab" is specified use only the "ab" language, otherwise |
1200 | * search all languages. */ |
1201 | if (orgpat.len > 3 && pat[orgpat.len - 3] == '@' |
1202 | && ASCII_ISALPHA(pat[orgpat.len - 2]) |
1203 | && ASCII_ISALPHA(pat[orgpat.len - 1])) { |
1204 | saved_pat = vim_strnsave(pat, orgpat.len - 3); |
1205 | help_lang_find = &pat[orgpat.len - 2]; |
1206 | orgpat.pat = saved_pat; |
1207 | orgpat.len -= 3; |
1208 | } |
1209 | } |
1210 | if (p_tl != 0 && orgpat.len > p_tl) /* adjust for 'taglength' */ |
1211 | orgpat.len = p_tl; |
1212 | |
1213 | save_emsg_off = emsg_off; |
1214 | emsg_off = TRUE; /* don't want error for invalid RE here */ |
1215 | prepare_pats(&orgpat, has_re); |
1216 | emsg_off = save_emsg_off; |
1217 | if (has_re && orgpat.regmatch.regprog == NULL) |
1218 | goto findtag_end; |
1219 | |
1220 | // This is only to avoid a compiler warning for using search_info |
1221 | // uninitialised. |
1222 | memset(&search_info, 0, 1); // -V512 |
1223 | |
1224 | /* |
1225 | * When finding a specified number of matches, first try with matching |
1226 | * case, so binary search can be used, and try ignore-case matches in a |
1227 | * second loop. |
1228 | * When finding all matches, 'tagbsearch' is off, or there is no fixed |
1229 | * string to look for, ignore case right away to avoid going though the |
1230 | * tags files twice. |
1231 | * When the tag file is case-fold sorted, it is either one or the other. |
1232 | * Only ignore case when TAG_NOIC not used or 'ignorecase' set. |
1233 | */ |
1234 | // Set a flag if the file extension is .txt |
1235 | if ((flags & TAG_KEEP_LANG) |
1236 | && help_lang_find == NULL |
1237 | && curbuf->b_fname != NULL |
1238 | && (i = (int)STRLEN(curbuf->b_fname)) > 4 |
1239 | && STRICMP(curbuf->b_fname + i - 4, ".txt" ) == 0) { |
1240 | is_txt = true; |
1241 | } |
1242 | orgpat.regmatch.rm_ic = ((p_ic || !noic) |
1243 | && (findall || orgpat.headlen == 0 || !p_tbs)); |
1244 | for (round = 1; round <= 2; ++round) { |
1245 | linear = (orgpat.headlen == 0 || !p_tbs || round == 2); |
1246 | |
1247 | // Try tag file names from tags option one by one. |
1248 | for (first_file = true; |
1249 | use_cscope || get_tagfname(&tn, first_file, tag_fname) == OK; |
1250 | first_file = false) { |
1251 | // A file that doesn't exist is silently ignored. Only when not a |
1252 | // single file is found, an error message is given (further on). |
1253 | if (use_cscope) { |
1254 | fp = NULL; // avoid GCC warning |
1255 | } else { |
1256 | if (curbuf->b_help) { |
1257 | // Keep en if the file extension is .txt |
1258 | if (is_txt) { |
1259 | STRCPY(help_lang, "en" ); |
1260 | } else { |
1261 | // Prefer help tags according to 'helplang'. Put the |
1262 | // two-letter language name in help_lang[]. |
1263 | i = (int)STRLEN(tag_fname); |
1264 | if (i > 3 && tag_fname[i - 3] == '-') { |
1265 | STRCPY(help_lang, tag_fname + i - 2); |
1266 | } else { |
1267 | STRCPY(help_lang, "en" ); |
1268 | } |
1269 | } |
1270 | |
1271 | /* When searching for a specific language skip tags files |
1272 | * for other languages. */ |
1273 | if (help_lang_find != NULL |
1274 | && STRICMP(help_lang, help_lang_find) != 0) |
1275 | continue; |
1276 | |
1277 | /* For CTRL-] in a help file prefer a match with the same |
1278 | * language. */ |
1279 | if ((flags & TAG_KEEP_LANG) |
1280 | && help_lang_find == NULL |
1281 | && curbuf->b_fname != NULL |
1282 | && (i = (int)STRLEN(curbuf->b_fname)) > 4 |
1283 | && curbuf->b_fname[i - 1] == 'x' |
1284 | && curbuf->b_fname[i - 4] == '.' |
1285 | && STRNICMP(curbuf->b_fname + i - 3, help_lang, 2) == 0) |
1286 | help_pri = 0; |
1287 | else { |
1288 | help_pri = 1; |
1289 | for (s = p_hlg; *s != NUL; ++s) { |
1290 | if (STRNICMP(s, help_lang, 2) == 0) |
1291 | break; |
1292 | ++help_pri; |
1293 | if ((s = vim_strchr(s, ',')) == NULL) |
1294 | break; |
1295 | } |
1296 | if (s == NULL || *s == NUL) { |
1297 | /* Language not in 'helplang': use last, prefer English, |
1298 | * unless found already. */ |
1299 | ++help_pri; |
1300 | if (STRICMP(help_lang, "en" ) != 0) |
1301 | ++help_pri; |
1302 | } |
1303 | } |
1304 | } |
1305 | |
1306 | if ((fp = os_fopen((char *)tag_fname, "r" )) == NULL) { |
1307 | continue; |
1308 | } |
1309 | |
1310 | if (p_verbose >= 5) { |
1311 | verbose_enter(); |
1312 | smsg(_("Searching tags file %s" ), tag_fname); |
1313 | verbose_leave(); |
1314 | } |
1315 | } |
1316 | did_open = TRUE; /* remember that we found at least one file */ |
1317 | |
1318 | state = TS_START; /* we're at the start of the file */ |
1319 | |
1320 | /* |
1321 | * Read and parse the lines in the file one by one |
1322 | */ |
1323 | for (;; ) { |
1324 | // check for CTRL-C typed, more often when jumping around |
1325 | if (state == TS_BINARY || state == TS_SKIP_BACK) { |
1326 | line_breakcheck(); |
1327 | } else { |
1328 | fast_breakcheck(); |
1329 | } |
1330 | if ((flags & TAG_INS_COMP)) /* Double brackets for gcc */ |
1331 | ins_compl_check_keys(30, false); |
1332 | if (got_int || compl_interrupted) { |
1333 | stop_searching = TRUE; |
1334 | break; |
1335 | } |
1336 | /* When mincount is TAG_MANY, stop when enough matches have been |
1337 | * found (for completion). */ |
1338 | if (mincount == TAG_MANY && match_count >= TAG_MANY) { |
1339 | stop_searching = TRUE; |
1340 | retval = OK; |
1341 | break; |
1342 | } |
1343 | if (get_it_again) |
1344 | goto line_read_in; |
1345 | /* |
1346 | * For binary search: compute the next offset to use. |
1347 | */ |
1348 | if (state == TS_BINARY) { |
1349 | offset = search_info.low_offset + ((search_info.high_offset |
1350 | - search_info.low_offset) / 2); |
1351 | if (offset == search_info.curr_offset) |
1352 | break; /* End the binary search without a match. */ |
1353 | else |
1354 | search_info.curr_offset = offset; |
1355 | } |
1356 | /* |
1357 | * Skipping back (after a match during binary search). |
1358 | */ |
1359 | else if (state == TS_SKIP_BACK) { |
1360 | search_info.curr_offset -= LSIZE * 2; |
1361 | if (search_info.curr_offset < 0) { |
1362 | search_info.curr_offset = 0; |
1363 | rewind(fp); |
1364 | state = TS_STEP_FORWARD; |
1365 | } |
1366 | } |
1367 | |
1368 | /* |
1369 | * When jumping around in the file, first read a line to find the |
1370 | * start of the next line. |
1371 | */ |
1372 | if (state == TS_BINARY || state == TS_SKIP_BACK) { |
1373 | /* Adjust the search file offset to the correct position */ |
1374 | search_info.curr_offset_used = search_info.curr_offset; |
1375 | vim_fseek(fp, search_info.curr_offset, SEEK_SET); |
1376 | eof = vim_fgets(lbuf, LSIZE, fp); |
1377 | if (!eof && search_info.curr_offset != 0) { |
1378 | /* The explicit cast is to work around a bug in gcc 3.4.2 |
1379 | * (repeated below). */ |
1380 | search_info.curr_offset = vim_ftell(fp); |
1381 | if (search_info.curr_offset == search_info.high_offset) { |
1382 | // oops, gone a bit too far; try from low offset |
1383 | vim_fseek(fp, search_info.low_offset, SEEK_SET); |
1384 | search_info.curr_offset = search_info.low_offset; |
1385 | } |
1386 | eof = vim_fgets(lbuf, LSIZE, fp); |
1387 | } |
1388 | /* skip empty and blank lines */ |
1389 | while (!eof && vim_isblankline(lbuf)) { |
1390 | search_info.curr_offset = vim_ftell(fp); |
1391 | eof = vim_fgets(lbuf, LSIZE, fp); |
1392 | } |
1393 | if (eof) { |
1394 | /* Hit end of file. Skip backwards. */ |
1395 | state = TS_SKIP_BACK; |
1396 | search_info.match_offset = vim_ftell(fp); |
1397 | search_info.curr_offset = search_info.curr_offset_used; |
1398 | continue; |
1399 | } |
1400 | } |
1401 | /* |
1402 | * Not jumping around in the file: Read the next line. |
1403 | */ |
1404 | else { |
1405 | /* skip empty and blank lines */ |
1406 | do { |
1407 | if (use_cscope) |
1408 | eof = cs_fgets(lbuf, LSIZE); |
1409 | else |
1410 | eof = vim_fgets(lbuf, LSIZE, fp); |
1411 | } while (!eof && vim_isblankline(lbuf)); |
1412 | |
1413 | if (eof) { |
1414 | break; /* end of file */ |
1415 | } |
1416 | } |
1417 | line_read_in: |
1418 | |
1419 | if (vimconv.vc_type != CONV_NONE) { |
1420 | char_u *conv_line; |
1421 | int len; |
1422 | |
1423 | /* Convert every line. Converting the pattern from 'enc' to |
1424 | * the tags file encoding doesn't work, because characters are |
1425 | * not recognized. */ |
1426 | conv_line = string_convert(&vimconv, lbuf, NULL); |
1427 | if (conv_line != NULL) { |
1428 | /* Copy or swap lbuf and conv_line. */ |
1429 | len = (int)STRLEN(conv_line) + 1; |
1430 | if (len > lbuf_size) { |
1431 | xfree(lbuf); |
1432 | lbuf = conv_line; |
1433 | lbuf_size = len; |
1434 | } else { |
1435 | STRCPY(lbuf, conv_line); |
1436 | xfree(conv_line); |
1437 | } |
1438 | } |
1439 | } |
1440 | |
1441 | |
1442 | |
1443 | /* |
1444 | * When still at the start of the file, check for Emacs tags file |
1445 | * format, and for "not sorted" flag. |
1446 | */ |
1447 | if (state == TS_START) { |
1448 | /* The header ends when the line sorts below "!_TAG_". When |
1449 | * case is folded lower case letters sort before "_". */ |
1450 | if (STRNCMP(lbuf, "!_TAG_" , 6) <= 0 |
1451 | || (lbuf[0] == '!' && ASCII_ISLOWER(lbuf[1]))) { |
1452 | if (STRNCMP(lbuf, "!_TAG_" , 6) != 0) |
1453 | /* Non-header item before the header, e.g. "!" itself. |
1454 | */ |
1455 | goto parse_line; |
1456 | |
1457 | /* |
1458 | * Read header line. |
1459 | */ |
1460 | if (STRNCMP(lbuf, "!_TAG_FILE_SORTED\t" , 18) == 0) |
1461 | tag_file_sorted = lbuf[18]; |
1462 | if (STRNCMP(lbuf, "!_TAG_FILE_ENCODING\t" , 20) == 0) { |
1463 | /* Prepare to convert every line from the specified |
1464 | * encoding to 'encoding'. */ |
1465 | for (p = lbuf + 20; *p > ' ' && *p < 127; ++p) |
1466 | ; |
1467 | *p = NUL; |
1468 | convert_setup(&vimconv, lbuf + 20, p_enc); |
1469 | } |
1470 | |
1471 | /* Read the next line. Unrecognized flags are ignored. */ |
1472 | continue; |
1473 | } |
1474 | |
1475 | /* Headers ends. */ |
1476 | |
1477 | /* |
1478 | * When there is no tag head, or ignoring case, need to do a |
1479 | * linear search. |
1480 | * When no "!_TAG_" is found, default to binary search. If |
1481 | * the tag file isn't sorted, the second loop will find it. |
1482 | * When "!_TAG_FILE_SORTED" found: start binary search if |
1483 | * flag set. |
1484 | * For cscope, it's always linear. |
1485 | */ |
1486 | if (linear || use_cscope) |
1487 | state = TS_LINEAR; |
1488 | else if (tag_file_sorted == NUL) |
1489 | state = TS_BINARY; |
1490 | else if (tag_file_sorted == '1') |
1491 | state = TS_BINARY; |
1492 | else if (tag_file_sorted == '2') { |
1493 | state = TS_BINARY; |
1494 | sortic = TRUE; |
1495 | orgpat.regmatch.rm_ic = (p_ic || !noic); |
1496 | } else |
1497 | state = TS_LINEAR; |
1498 | |
1499 | if (state == TS_BINARY && orgpat.regmatch.rm_ic && !sortic) { |
1500 | /* Binary search won't work for ignoring case, use linear |
1501 | * search. */ |
1502 | linear = TRUE; |
1503 | state = TS_LINEAR; |
1504 | } |
1505 | |
1506 | /* |
1507 | * When starting a binary search, get the size of the file and |
1508 | * compute the first offset. |
1509 | */ |
1510 | if (state == TS_BINARY) { |
1511 | // Get the tag file size. |
1512 | if ((filesize = vim_lseek(fileno(fp), (off_T)0L, SEEK_END)) <= 0) { |
1513 | state = TS_LINEAR; |
1514 | } else { |
1515 | vim_lseek(fileno(fp), (off_T)0L, SEEK_SET); |
1516 | |
1517 | /* Calculate the first read offset in the file. Start |
1518 | * the search in the middle of the file. */ |
1519 | search_info.low_offset = 0; |
1520 | search_info.low_char = 0; |
1521 | search_info.high_offset = filesize; |
1522 | search_info.curr_offset = 0; |
1523 | search_info.high_char = 0xff; |
1524 | } |
1525 | continue; |
1526 | } |
1527 | } |
1528 | |
1529 | parse_line: |
1530 | // When the line is too long the NUL will not be in the |
1531 | // last-but-one byte (see vim_fgets()). |
1532 | // Has been reported for Mozilla JS with extremely long names. |
1533 | // In that case we can't parse it and we ignore the line. |
1534 | if (lbuf[LSIZE - 2] != NUL && !use_cscope) { |
1535 | if (p_verbose >= 5) { |
1536 | verbose_enter(); |
1537 | MSG(_("Ignoring long line in tags file" )); |
1538 | verbose_leave(); |
1539 | } |
1540 | if (state != TS_LINEAR) { |
1541 | // Avoid getting stuck. |
1542 | linear = true; |
1543 | state = TS_LINEAR; |
1544 | vim_fseek(fp, search_info.low_offset, SEEK_SET); |
1545 | } |
1546 | continue; |
1547 | } |
1548 | |
1549 | // Figure out where the different strings are in this line. |
1550 | // For "normal" tags: Do a quick check if the tag matches. |
1551 | // This speeds up tag searching a lot! |
1552 | if (orgpat.headlen) { |
1553 | tagp.tagname = lbuf; |
1554 | tagp.tagname_end = vim_strchr(lbuf, TAB); |
1555 | if (tagp.tagname_end == NULL) { |
1556 | // Corrupted tag line. |
1557 | line_error = true; |
1558 | break; |
1559 | } |
1560 | |
1561 | /* |
1562 | * Skip this line if the length of the tag is different and |
1563 | * there is no regexp, or the tag is too short. |
1564 | */ |
1565 | cmplen = (int)(tagp.tagname_end - tagp.tagname); |
1566 | if (p_tl != 0 && cmplen > p_tl) /* adjust for 'taglength' */ |
1567 | cmplen = p_tl; |
1568 | if (has_re && orgpat.headlen < cmplen) |
1569 | cmplen = orgpat.headlen; |
1570 | else if (state == TS_LINEAR && orgpat.headlen != cmplen) |
1571 | continue; |
1572 | |
1573 | if (state == TS_BINARY) { |
1574 | /* |
1575 | * Simplistic check for unsorted tags file. |
1576 | */ |
1577 | i = (int)tagp.tagname[0]; |
1578 | if (sortic) |
1579 | i = TOUPPER_ASC(tagp.tagname[0]); |
1580 | if (i < search_info.low_char || i > search_info.high_char) |
1581 | sort_error = TRUE; |
1582 | |
1583 | /* |
1584 | * Compare the current tag with the searched tag. |
1585 | */ |
1586 | if (sortic) |
1587 | tagcmp = tag_strnicmp(tagp.tagname, orgpat.head, |
1588 | (size_t)cmplen); |
1589 | else |
1590 | tagcmp = STRNCMP(tagp.tagname, orgpat.head, cmplen); |
1591 | |
1592 | /* |
1593 | * A match with a shorter tag means to search forward. |
1594 | * A match with a longer tag means to search backward. |
1595 | */ |
1596 | if (tagcmp == 0) { |
1597 | if (cmplen < orgpat.headlen) |
1598 | tagcmp = -1; |
1599 | else if (cmplen > orgpat.headlen) |
1600 | tagcmp = 1; |
1601 | } |
1602 | |
1603 | if (tagcmp == 0) { |
1604 | /* We've located the tag, now skip back and search |
1605 | * forward until the first matching tag is found. |
1606 | */ |
1607 | state = TS_SKIP_BACK; |
1608 | search_info.match_offset = search_info.curr_offset; |
1609 | continue; |
1610 | } |
1611 | if (tagcmp < 0) { |
1612 | search_info.curr_offset = vim_ftell(fp); |
1613 | if (search_info.curr_offset < search_info.high_offset) { |
1614 | search_info.low_offset = search_info.curr_offset; |
1615 | if (sortic) |
1616 | search_info.low_char = |
1617 | TOUPPER_ASC(tagp.tagname[0]); |
1618 | else |
1619 | search_info.low_char = tagp.tagname[0]; |
1620 | continue; |
1621 | } |
1622 | } |
1623 | if (tagcmp > 0 |
1624 | && search_info.curr_offset != search_info.high_offset) { |
1625 | search_info.high_offset = search_info.curr_offset; |
1626 | if (sortic) |
1627 | search_info.high_char = |
1628 | TOUPPER_ASC(tagp.tagname[0]); |
1629 | else |
1630 | search_info.high_char = tagp.tagname[0]; |
1631 | continue; |
1632 | } |
1633 | |
1634 | /* No match yet and are at the end of the binary search. */ |
1635 | break; |
1636 | } else if (state == TS_SKIP_BACK) { |
1637 | assert(cmplen >= 0); |
1638 | if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)cmplen) != 0) |
1639 | state = TS_STEP_FORWARD; |
1640 | else |
1641 | /* Have to skip back more. Restore the curr_offset |
1642 | * used, otherwise we get stuck at a long line. */ |
1643 | search_info.curr_offset = search_info.curr_offset_used; |
1644 | continue; |
1645 | } else if (state == TS_STEP_FORWARD) { |
1646 | assert(cmplen >= 0); |
1647 | if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)cmplen) != 0) { |
1648 | if ((off_T)vim_ftell(fp) > search_info.match_offset) { |
1649 | break; // past last match |
1650 | } else { |
1651 | continue; // before first match |
1652 | } |
1653 | } |
1654 | } else |
1655 | /* skip this match if it can't match */ |
1656 | assert(cmplen >= 0); |
1657 | if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)cmplen) != 0) |
1658 | continue; |
1659 | |
1660 | // Can be a matching tag, isolate the file name and command. |
1661 | tagp.fname = tagp.tagname_end + 1; |
1662 | tagp.fname_end = vim_strchr(tagp.fname, TAB); |
1663 | tagp.command = tagp.fname_end + 1; |
1664 | if (tagp.fname_end == NULL) |
1665 | i = FAIL; |
1666 | else |
1667 | i = OK; |
1668 | } else |
1669 | i = parse_tag_line(lbuf, |
1670 | &tagp); |
1671 | if (i == FAIL) { |
1672 | line_error = TRUE; |
1673 | break; |
1674 | } |
1675 | |
1676 | /* |
1677 | * First try matching with the pattern literally (also when it is |
1678 | * a regexp). |
1679 | */ |
1680 | cmplen = (int)(tagp.tagname_end - tagp.tagname); |
1681 | if (p_tl != 0 && cmplen > p_tl) /* adjust for 'taglength' */ |
1682 | cmplen = p_tl; |
1683 | /* if tag length does not match, don't try comparing */ |
1684 | if (orgpat.len != cmplen) |
1685 | match = FALSE; |
1686 | else { |
1687 | if (orgpat.regmatch.rm_ic) { |
1688 | assert(cmplen >= 0); |
1689 | match = mb_strnicmp(tagp.tagname, orgpat.pat, (size_t)cmplen) == 0; |
1690 | if (match) |
1691 | match_no_ic = (STRNCMP(tagp.tagname, orgpat.pat, |
1692 | cmplen) == 0); |
1693 | } else |
1694 | match = (STRNCMP(tagp.tagname, orgpat.pat, cmplen) == 0); |
1695 | } |
1696 | |
1697 | /* |
1698 | * Has a regexp: Also find tags matching regexp. |
1699 | */ |
1700 | match_re = FALSE; |
1701 | if (!match && orgpat.regmatch.regprog != NULL) { |
1702 | int cc; |
1703 | |
1704 | cc = *tagp.tagname_end; |
1705 | *tagp.tagname_end = NUL; |
1706 | match = vim_regexec(&orgpat.regmatch, tagp.tagname, (colnr_T)0); |
1707 | if (match) { |
1708 | matchoff = (int)(orgpat.regmatch.startp[0] - tagp.tagname); |
1709 | if (orgpat.regmatch.rm_ic) { |
1710 | orgpat.regmatch.rm_ic = FALSE; |
1711 | match_no_ic = vim_regexec(&orgpat.regmatch, tagp.tagname, |
1712 | (colnr_T)0); |
1713 | orgpat.regmatch.rm_ic = TRUE; |
1714 | } |
1715 | } |
1716 | *tagp.tagname_end = cc; |
1717 | match_re = TRUE; |
1718 | } |
1719 | |
1720 | // If a match is found, add it to ht_match[] and ga_match[]. |
1721 | if (match) { |
1722 | int len = 0; |
1723 | |
1724 | if (use_cscope) { |
1725 | /* Don't change the ordering, always use the same table. */ |
1726 | mtt = MT_GL_OTH; |
1727 | } else { |
1728 | // Decide in which array to store this match. |
1729 | is_current = test_for_current(tagp.fname, tagp.fname_end, tag_fname, |
1730 | buf_ffname); |
1731 | is_static = test_for_static(&tagp); |
1732 | |
1733 | // Decide in which of the sixteen tables to store this match. |
1734 | if (is_static) { |
1735 | if (is_current) |
1736 | mtt = MT_ST_CUR; |
1737 | else |
1738 | mtt = MT_ST_OTH; |
1739 | } else { |
1740 | if (is_current) |
1741 | mtt = MT_GL_CUR; |
1742 | else |
1743 | mtt = MT_GL_OTH; |
1744 | } |
1745 | if (orgpat.regmatch.rm_ic && !match_no_ic) |
1746 | mtt += MT_IC_OFF; |
1747 | if (match_re) |
1748 | mtt += MT_RE_OFF; |
1749 | } |
1750 | |
1751 | // Add the found match in ht_match[mtt] and ga_match[mtt]. |
1752 | // Store the info we need later, which depends on the kind of |
1753 | // tags we are dealing with. |
1754 | if (help_only) { |
1755 | # define 3 |
1756 | // Append the help-heuristic number after the tagname, for |
1757 | // sorting it later. The heuristic is ignored for |
1758 | // detecting duplicates. |
1759 | // The format is {tagname}@{lang}NUL{heuristic}NUL |
1760 | *tagp.tagname_end = NUL; |
1761 | len = (int)(tagp.tagname_end - tagp.tagname); |
1762 | mfp = xmalloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1); |
1763 | |
1764 | p = mfp; |
1765 | STRCPY(p, tagp.tagname); |
1766 | p[len] = '@'; |
1767 | STRCPY(p + len + 1, help_lang); |
1768 | snprintf((char *)p + len + 1 + ML_EXTRA, 10, "%06d" , |
1769 | help_heuristic(tagp.tagname, |
1770 | match_re ? matchoff : 0, !match_no_ic) |
1771 | + help_pri); |
1772 | |
1773 | *tagp.tagname_end = TAB; |
1774 | } else if (name_only) { |
1775 | if (get_it_again) { |
1776 | char_u *temp_end = tagp.command; |
1777 | |
1778 | if (*temp_end == '/') { |
1779 | while (*temp_end && *temp_end != '\r' |
1780 | && *temp_end != '\n' |
1781 | && *temp_end != '$') { |
1782 | temp_end++; |
1783 | } |
1784 | } |
1785 | |
1786 | if (tagp.command + 2 < temp_end) { |
1787 | len = (int)(temp_end - tagp.command - 2); |
1788 | mfp = xmalloc(len + 2); |
1789 | STRLCPY(mfp, tagp.command + 2, len + 1); |
1790 | } else { |
1791 | mfp = NULL; |
1792 | } |
1793 | get_it_again = false; |
1794 | } else { |
1795 | len = (int)(tagp.tagname_end - tagp.tagname); |
1796 | mfp = xmalloc(sizeof(char_u) + len + 1); |
1797 | STRLCPY(mfp, tagp.tagname, len + 1); |
1798 | |
1799 | // if wanted, re-read line to get long form too |
1800 | if (State & INSERT) { |
1801 | get_it_again = p_sft; |
1802 | } |
1803 | } |
1804 | } else { |
1805 | #define TAG_SEP 0x02 |
1806 | size_t tag_fname_len = STRLEN(tag_fname); |
1807 | // Save the tag in a buffer. |
1808 | // Use 0x02 to separate fields (Can't use NUL, because the |
1809 | // hash key is terminated by NUL). |
1810 | // Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL> |
1811 | // other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL> |
1812 | // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL> |
1813 | // Here <mtt> is the "mtt" value plus 1 to avoid NUL. |
1814 | len = (int)tag_fname_len + (int)STRLEN(lbuf) + 3; |
1815 | mfp = xmalloc(sizeof(char_u) + len + 1); |
1816 | p = mfp; |
1817 | p[0] = mtt + 1; |
1818 | STRCPY(p + 1, tag_fname); |
1819 | #ifdef BACKSLASH_IN_FILENAME |
1820 | // Ignore differences in slashes, avoid adding |
1821 | // both path/file and path\file. |
1822 | slash_adjust(p + 1); |
1823 | #endif |
1824 | p[tag_fname_len + 1] = TAG_SEP; |
1825 | s = p + 1 + tag_fname_len + 1; |
1826 | STRCPY(s, lbuf); |
1827 | } |
1828 | |
1829 | if (mfp != NULL) { |
1830 | hashitem_T *hi; |
1831 | |
1832 | // Don't add identical matches. |
1833 | // Add all cscope tags, because they are all listed. |
1834 | // "mfp" is used as a hash key, there is a NUL byte to end |
1835 | // the part that matters for comparing, more bytes may |
1836 | // follow after it. E.g. help tags store the priority |
1837 | // after the NUL. |
1838 | if (use_cscope) { |
1839 | hash++; |
1840 | } else { |
1841 | hash = hash_hash(mfp); |
1842 | } |
1843 | hi = hash_lookup(&ht_match[mtt], (const char *)mfp, |
1844 | STRLEN(mfp), hash); |
1845 | if (HASHITEM_EMPTY(hi)) { |
1846 | hash_add_item(&ht_match[mtt], hi, mfp, hash); |
1847 | ga_grow(&ga_match[mtt], 1); |
1848 | ((char_u **)(ga_match[mtt].ga_data)) |
1849 | [ga_match[mtt].ga_len++] = mfp; |
1850 | match_count++; |
1851 | } else { |
1852 | // duplicate tag, drop it |
1853 | xfree(mfp); |
1854 | } |
1855 | } |
1856 | } |
1857 | if (use_cscope && eof) |
1858 | break; |
1859 | } /* forever */ |
1860 | |
1861 | if (line_error) { |
1862 | EMSG2(_("E431: Format error in tags file \"%s\"" ), tag_fname); |
1863 | if (!use_cscope) { |
1864 | EMSGN(_("Before byte %" PRId64), vim_ftell(fp)); |
1865 | } |
1866 | stop_searching = true; |
1867 | line_error = false; |
1868 | } |
1869 | |
1870 | if (!use_cscope) |
1871 | fclose(fp); |
1872 | if (vimconv.vc_type != CONV_NONE) |
1873 | convert_setup(&vimconv, NULL, NULL); |
1874 | |
1875 | tag_file_sorted = NUL; |
1876 | if (sort_error) { |
1877 | EMSG2(_("E432: Tags file not sorted: %s" ), tag_fname); |
1878 | sort_error = FALSE; |
1879 | } |
1880 | |
1881 | /* |
1882 | * Stop searching if sufficient tags have been found. |
1883 | */ |
1884 | if (match_count >= mincount) { |
1885 | retval = OK; |
1886 | stop_searching = TRUE; |
1887 | } |
1888 | |
1889 | if (stop_searching || use_cscope) |
1890 | break; |
1891 | |
1892 | } /* end of for-each-file loop */ |
1893 | |
1894 | if (!use_cscope) |
1895 | tagname_free(&tn); |
1896 | |
1897 | /* stop searching when already did a linear search, or when TAG_NOIC |
1898 | * used, and 'ignorecase' not set or already did case-ignore search */ |
1899 | if (stop_searching || linear || (!p_ic && noic) || orgpat.regmatch.rm_ic) |
1900 | break; |
1901 | if (use_cscope) |
1902 | break; |
1903 | orgpat.regmatch.rm_ic = TRUE; /* try another time while ignoring case */ |
1904 | } |
1905 | |
1906 | if (!stop_searching) { |
1907 | if (!did_open && verbose) /* never opened any tags file */ |
1908 | EMSG(_("E433: No tags file" )); |
1909 | retval = OK; /* It's OK even when no tag found */ |
1910 | } |
1911 | |
1912 | findtag_end: |
1913 | xfree(lbuf); |
1914 | vim_regfree(orgpat.regmatch.regprog); |
1915 | xfree(tag_fname); |
1916 | |
1917 | /* |
1918 | * Move the matches from the ga_match[] arrays into one list of |
1919 | * matches. When retval == FAIL, free the matches. |
1920 | */ |
1921 | if (retval == FAIL) |
1922 | match_count = 0; |
1923 | |
1924 | if (match_count > 0) |
1925 | matches = xmalloc(match_count * sizeof(char_u *)); |
1926 | else |
1927 | matches = NULL; |
1928 | match_count = 0; |
1929 | for (mtt = 0; mtt < MT_COUNT; mtt++) { |
1930 | for (i = 0; i < ga_match[mtt].ga_len; i++) { |
1931 | mfp = ((char_u **)(ga_match[mtt].ga_data))[i]; |
1932 | if (matches == NULL) { |
1933 | xfree(mfp); |
1934 | } else { |
1935 | if (!name_only) { |
1936 | // Change mtt back to zero-based. |
1937 | *mfp = *mfp - 1; |
1938 | |
1939 | // change the TAG_SEP back to NUL |
1940 | for (p = mfp + 1; *p != NUL; p++) { |
1941 | if (*p == TAG_SEP) { |
1942 | *p = NUL; |
1943 | } |
1944 | } |
1945 | } |
1946 | matches[match_count++] = (char_u *)mfp; |
1947 | } |
1948 | } |
1949 | |
1950 | ga_clear(&ga_match[mtt]); |
1951 | hash_clear(&ht_match[mtt]); |
1952 | } |
1953 | |
1954 | *matchesp = matches; |
1955 | *num_matches = match_count; |
1956 | |
1957 | curbuf->b_help = help_save; |
1958 | xfree(saved_pat); |
1959 | |
1960 | p_ic = save_p_ic; |
1961 | |
1962 | return retval; |
1963 | } |
1964 | |
1965 | static garray_T tag_fnames = GA_EMPTY_INIT_VALUE; |
1966 | |
1967 | /* |
1968 | * Callback function for finding all "tags" and "tags-??" files in |
1969 | * 'runtimepath' doc directories. |
1970 | */ |
1971 | static void found_tagfile_cb(char_u *fname, void *cookie) |
1972 | { |
1973 | char_u *const tag_fname = vim_strsave(fname); |
1974 | |
1975 | #ifdef BACKSLASH_IN_FILENAME |
1976 | slash_adjust(tag_fname); |
1977 | #endif |
1978 | simplify_filename(tag_fname); |
1979 | GA_APPEND(char_u *, &tag_fnames, tag_fname); |
1980 | } |
1981 | |
1982 | #if defined(EXITFREE) |
1983 | void free_tag_stuff(void) |
1984 | { |
1985 | ga_clear_strings(&tag_fnames); |
1986 | do_tag(NULL, DT_FREE, 0, 0, 0); |
1987 | tag_freematch(); |
1988 | |
1989 | if (ptag_entry.tagname) { |
1990 | XFREE_CLEAR(ptag_entry.tagname); |
1991 | } |
1992 | } |
1993 | |
1994 | #endif |
1995 | |
1996 | /* |
1997 | * Get the next name of a tag file from the tag file list. |
1998 | * For help files, use "tags" file only. |
1999 | * |
2000 | * Return FAIL if no more tag file names, OK otherwise. |
2001 | */ |
2002 | int |
2003 | get_tagfname ( |
2004 | tagname_T *tnp, /* holds status info */ |
2005 | int first, /* TRUE when first file name is wanted */ |
2006 | char_u *buf /* pointer to buffer of MAXPATHL chars */ |
2007 | ) |
2008 | { |
2009 | char_u *fname = NULL; |
2010 | char_u *r_ptr; |
2011 | |
2012 | if (first) |
2013 | memset(tnp, 0, sizeof(tagname_T)); |
2014 | |
2015 | if (curbuf->b_help) { |
2016 | /* |
2017 | * For help files it's done in a completely different way: |
2018 | * Find "doc/tags" and "doc/tags-??" in all directories in |
2019 | * 'runtimepath'. |
2020 | */ |
2021 | if (first) { |
2022 | ga_clear_strings(&tag_fnames); |
2023 | ga_init(&tag_fnames, (int)sizeof(char_u *), 10); |
2024 | do_in_runtimepath((char_u *)"doc/tags doc/tags-??" , DIP_ALL, |
2025 | found_tagfile_cb, NULL); |
2026 | } |
2027 | |
2028 | if (tnp->tn_hf_idx >= tag_fnames.ga_len) { |
2029 | /* Not found in 'runtimepath', use 'helpfile', if it exists and |
2030 | * wasn't used yet, replacing "help.txt" with "tags". */ |
2031 | if (tnp->tn_hf_idx > tag_fnames.ga_len || *p_hf == NUL) |
2032 | return FAIL; |
2033 | ++tnp->tn_hf_idx; |
2034 | STRCPY(buf, p_hf); |
2035 | STRCPY(path_tail(buf), "tags" ); |
2036 | #ifdef BACKSLASH_IN_FILENAME |
2037 | slash_adjust(buf); |
2038 | #endif |
2039 | simplify_filename(buf); |
2040 | |
2041 | for (int i = 0; i < tag_fnames.ga_len; i++) { |
2042 | if (STRCMP(buf, ((char_u **)(tag_fnames.ga_data))[i]) == 0) { |
2043 | return FAIL; // avoid duplicate file names |
2044 | } |
2045 | } |
2046 | } else { |
2047 | STRLCPY(buf, ((char_u **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], |
2048 | MAXPATHL); |
2049 | } |
2050 | return OK; |
2051 | } |
2052 | |
2053 | if (first) { |
2054 | /* Init. We make a copy of 'tags', because autocommands may change |
2055 | * the value without notifying us. */ |
2056 | tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL) |
2057 | ? curbuf->b_p_tags : p_tags); |
2058 | tnp->tn_np = tnp->tn_tags; |
2059 | } |
2060 | |
2061 | /* |
2062 | * Loop until we have found a file name that can be used. |
2063 | * There are two states: |
2064 | * tnp->tn_did_filefind_init == FALSE: setup for next part in 'tags'. |
2065 | * tnp->tn_did_filefind_init == TRUE: find next file in this part. |
2066 | */ |
2067 | for (;; ) { |
2068 | if (tnp->tn_did_filefind_init) { |
2069 | fname = vim_findfile(tnp->tn_search_ctx); |
2070 | if (fname != NULL) |
2071 | break; |
2072 | |
2073 | tnp->tn_did_filefind_init = FALSE; |
2074 | } else { |
2075 | char_u *filename = NULL; |
2076 | |
2077 | /* Stop when used all parts of 'tags'. */ |
2078 | if (*tnp->tn_np == NUL) { |
2079 | vim_findfile_cleanup(tnp->tn_search_ctx); |
2080 | tnp->tn_search_ctx = NULL; |
2081 | return FAIL; |
2082 | } |
2083 | |
2084 | /* |
2085 | * Copy next file name into buf. |
2086 | */ |
2087 | buf[0] = NUL; |
2088 | (void)copy_option_part(&tnp->tn_np, buf, MAXPATHL - 1, " ," ); |
2089 | |
2090 | r_ptr = vim_findfile_stopdir(buf); |
2091 | /* move the filename one char forward and truncate the |
2092 | * filepath with a NUL */ |
2093 | filename = path_tail(buf); |
2094 | STRMOVE(filename + 1, filename); |
2095 | *filename++ = NUL; |
2096 | |
2097 | tnp->tn_search_ctx = vim_findfile_init(buf, filename, |
2098 | r_ptr, 100, |
2099 | FALSE, /* don't free visited list */ |
2100 | FINDFILE_FILE, /* we search for a file */ |
2101 | tnp->tn_search_ctx, TRUE, curbuf->b_ffname); |
2102 | if (tnp->tn_search_ctx != NULL) |
2103 | tnp->tn_did_filefind_init = TRUE; |
2104 | } |
2105 | } |
2106 | |
2107 | STRCPY(buf, fname); |
2108 | xfree(fname); |
2109 | return OK; |
2110 | } |
2111 | |
2112 | /* |
2113 | * Free the contents of a tagname_T that was filled by get_tagfname(). |
2114 | */ |
2115 | void tagname_free(tagname_T *tnp) |
2116 | { |
2117 | xfree(tnp->tn_tags); |
2118 | vim_findfile_cleanup(tnp->tn_search_ctx); |
2119 | tnp->tn_search_ctx = NULL; |
2120 | ga_clear_strings(&tag_fnames); |
2121 | } |
2122 | |
2123 | /* |
2124 | * Parse one line from the tags file. Find start/end of tag name, start/end of |
2125 | * file name and start of search pattern. |
2126 | * |
2127 | * If is_etag is TRUE, tagp->fname and tagp->fname_end are not set. |
2128 | * |
2129 | * Return FAIL if there is a format error in this line, OK otherwise. |
2130 | */ |
2131 | static int |
2132 | parse_tag_line ( |
2133 | char_u *lbuf, /* line to be parsed */ |
2134 | tagptrs_T *tagp |
2135 | ) |
2136 | { |
2137 | char_u *p; |
2138 | |
2139 | /* Isolate the tagname, from lbuf up to the first white */ |
2140 | tagp->tagname = lbuf; |
2141 | p = vim_strchr(lbuf, TAB); |
2142 | if (p == NULL) |
2143 | return FAIL; |
2144 | tagp->tagname_end = p; |
2145 | |
2146 | /* Isolate file name, from first to second white space */ |
2147 | if (*p != NUL) |
2148 | ++p; |
2149 | tagp->fname = p; |
2150 | p = vim_strchr(p, TAB); |
2151 | if (p == NULL) |
2152 | return FAIL; |
2153 | tagp->fname_end = p; |
2154 | |
2155 | /* find start of search command, after second white space */ |
2156 | if (*p != NUL) |
2157 | ++p; |
2158 | if (*p == NUL) |
2159 | return FAIL; |
2160 | tagp->command = p; |
2161 | |
2162 | return OK; |
2163 | } |
2164 | |
2165 | /* |
2166 | * Check if tagname is a static tag |
2167 | * |
2168 | * Static tags produced by the older ctags program have the format: |
2169 | * 'file:tag file /pattern'. |
2170 | * This is only recognized when both occurrence of 'file' are the same, to |
2171 | * avoid recognizing "string::string" or ":exit". |
2172 | * |
2173 | * Static tags produced by the new ctags program have the format: |
2174 | * 'tag file /pattern/;"<Tab>file:' " |
2175 | * |
2176 | * Return TRUE if it is a static tag and adjust *tagname to the real tag. |
2177 | * Return FALSE if it is not a static tag. |
2178 | */ |
2179 | static bool test_for_static(tagptrs_T *tagp) |
2180 | { |
2181 | char_u *p; |
2182 | |
2183 | // Check for new style static tag ":...<Tab>file:[<Tab>...]" |
2184 | p = tagp->command; |
2185 | while ((p = vim_strchr(p, '\t')) != NULL) { |
2186 | ++p; |
2187 | if (STRNCMP(p, "file:" , 5) == 0) |
2188 | return TRUE; |
2189 | } |
2190 | |
2191 | return FALSE; |
2192 | } |
2193 | |
2194 | // Returns the length of a matching tag line. |
2195 | static size_t matching_line_len(const char_u *const lbuf) |
2196 | { |
2197 | const char_u *p = lbuf + 1; |
2198 | |
2199 | // does the same thing as parse_match() |
2200 | p += STRLEN(p) + 1; |
2201 | return (p - lbuf) + STRLEN(p); |
2202 | } |
2203 | |
2204 | /* |
2205 | * Parse a line from a matching tag. Does not change the line itself. |
2206 | * |
2207 | * The line that we get looks like this: |
2208 | * Emacs tag: <mtt><tag_fname><NUL><ebuf><NUL><lbuf> |
2209 | * other tag: <mtt><tag_fname><NUL><NUL><lbuf> |
2210 | * without Emacs tags: <mtt><tag_fname><NUL><lbuf> |
2211 | * |
2212 | * Return OK or FAIL. |
2213 | */ |
2214 | static int |
2215 | parse_match ( |
2216 | char_u *lbuf, /* input: matching line */ |
2217 | tagptrs_T *tagp /* output: pointers into the line */ |
2218 | ) |
2219 | { |
2220 | int retval; |
2221 | char_u *p; |
2222 | char_u *pc, *pt; |
2223 | |
2224 | tagp->tag_fname = lbuf + 1; |
2225 | lbuf += STRLEN(tagp->tag_fname) + 2; |
2226 | |
2227 | /* Find search pattern and the file name for non-etags. */ |
2228 | retval = parse_tag_line(lbuf, |
2229 | tagp); |
2230 | |
2231 | tagp->tagkind = NULL; |
2232 | tagp->command_end = NULL; |
2233 | |
2234 | if (retval == OK) { |
2235 | /* Try to find a kind field: "kind:<kind>" or just "<kind>"*/ |
2236 | p = tagp->command; |
2237 | if (find_extra(&p) == OK) { |
2238 | tagp->command_end = p; |
2239 | if (p > tagp->command && p[-1] == '|') { |
2240 | tagp->command_end = p - 1; // drop trailing bar |
2241 | } else { |
2242 | tagp->command_end = p; |
2243 | } |
2244 | p += 2; // skip ";\"" |
2245 | if (*p++ == TAB) { |
2246 | while (ASCII_ISALPHA(*p)) { |
2247 | if (STRNCMP(p, "kind:" , 5) == 0) { |
2248 | tagp->tagkind = p + 5; |
2249 | break; |
2250 | } |
2251 | pc = vim_strchr(p, ':'); |
2252 | pt = vim_strchr(p, '\t'); |
2253 | if (pc == NULL || (pt != NULL && pc > pt)) { |
2254 | tagp->tagkind = p; |
2255 | break; |
2256 | } |
2257 | if (pt == NULL) |
2258 | break; |
2259 | p = pt + 1; |
2260 | } |
2261 | } |
2262 | } |
2263 | if (tagp->tagkind != NULL) { |
2264 | for (p = tagp->tagkind; |
2265 | *p && *p != '\t' && *p != '\r' && *p != '\n'; ++p) |
2266 | ; |
2267 | tagp->tagkind_end = p; |
2268 | } |
2269 | } |
2270 | return retval; |
2271 | } |
2272 | |
2273 | /* |
2274 | * Find out the actual file name of a tag. Concatenate the tags file name |
2275 | * with the matching tag file name. |
2276 | * Returns an allocated string. |
2277 | */ |
2278 | static char_u *tag_full_fname(tagptrs_T *tagp) |
2279 | { |
2280 | int c = *tagp->fname_end; |
2281 | *tagp->fname_end = NUL; |
2282 | char_u *fullname = expand_tag_fname(tagp->fname, tagp->tag_fname, false); |
2283 | *tagp->fname_end = c; |
2284 | |
2285 | return fullname; |
2286 | } |
2287 | |
2288 | /* |
2289 | * Jump to a tag that has been found in one of the tag files |
2290 | * |
2291 | * returns OK for success, NOTAGFILE when file not found, FAIL otherwise. |
2292 | */ |
2293 | static int jumpto_tag( |
2294 | const char_u *lbuf_arg, // line from the tags file for this tag |
2295 | int forceit, // :ta with ! |
2296 | int keep_help // keep help flag (FALSE for cscope) |
2297 | ) |
2298 | { |
2299 | int save_secure; |
2300 | int save_magic; |
2301 | bool save_p_ws; |
2302 | int save_p_scs, save_p_ic; |
2303 | linenr_T save_lnum; |
2304 | char_u *str; |
2305 | char_u *pbuf; /* search pattern buffer */ |
2306 | char_u *pbuf_end; |
2307 | char_u *tofree_fname = NULL; |
2308 | char_u *fname; |
2309 | tagptrs_T tagp; |
2310 | int retval = FAIL; |
2311 | int getfile_result = GETFILE_UNUSED; |
2312 | int search_options; |
2313 | win_T *curwin_save = NULL; |
2314 | char_u *full_fname = NULL; |
2315 | const bool old_KeyTyped = KeyTyped; // getting the file may reset it |
2316 | const int l_g_do_tagpreview = g_do_tagpreview; |
2317 | const size_t len = matching_line_len(lbuf_arg) + 1; |
2318 | char_u *lbuf = xmalloc(len); |
2319 | memmove(lbuf, lbuf_arg, len); |
2320 | |
2321 | pbuf = xmalloc(LSIZE); |
2322 | |
2323 | /* parse the match line into the tagp structure */ |
2324 | if (parse_match(lbuf, &tagp) == FAIL) { |
2325 | tagp.fname_end = NULL; |
2326 | goto erret; |
2327 | } |
2328 | |
2329 | // truncate the file name, so it can be used as a string |
2330 | *tagp.fname_end = NUL; |
2331 | fname = tagp.fname; |
2332 | |
2333 | /* copy the command to pbuf[], remove trailing CR/NL */ |
2334 | str = tagp.command; |
2335 | for (pbuf_end = pbuf; *str && *str != '\n' && *str != '\r'; ) { |
2336 | *pbuf_end++ = *str++; |
2337 | } |
2338 | *pbuf_end = NUL; |
2339 | |
2340 | { |
2341 | /* |
2342 | * Remove the "<Tab>fieldname:value" stuff; we don't need it here. |
2343 | */ |
2344 | str = pbuf; |
2345 | if (find_extra(&str) == OK) { |
2346 | pbuf_end = str; |
2347 | *pbuf_end = NUL; |
2348 | } |
2349 | } |
2350 | |
2351 | /* |
2352 | * Expand file name, when needed (for environment variables). |
2353 | * If 'tagrelative' option set, may change file name. |
2354 | */ |
2355 | fname = expand_tag_fname(fname, tagp.tag_fname, true); |
2356 | tofree_fname = fname; // free() it later |
2357 | |
2358 | /* |
2359 | * Check if the file with the tag exists before abandoning the current |
2360 | * file. Also accept a file name for which there is a matching BufReadCmd |
2361 | * autocommand event (e.g., http://sys/file). |
2362 | */ |
2363 | if (!os_path_exists(fname) |
2364 | && !has_autocmd(EVENT_BUFREADCMD, fname, NULL) |
2365 | ) { |
2366 | retval = NOTAGFILE; |
2367 | xfree(nofile_fname); |
2368 | nofile_fname = vim_strsave(fname); |
2369 | goto erret; |
2370 | } |
2371 | |
2372 | ++RedrawingDisabled; |
2373 | |
2374 | |
2375 | if (l_g_do_tagpreview != 0) { |
2376 | postponed_split = 0; /* don't split again below */ |
2377 | curwin_save = curwin; /* Save current window */ |
2378 | |
2379 | /* |
2380 | * If we are reusing a window, we may change dir when |
2381 | * entering it (autocommands) so turn the tag filename |
2382 | * into a fullpath |
2383 | */ |
2384 | if (!curwin->w_p_pvw) { |
2385 | full_fname = (char_u *)FullName_save((char *)fname, FALSE); |
2386 | fname = full_fname; |
2387 | |
2388 | /* |
2389 | * Make the preview window the current window. |
2390 | * Open a preview window when needed. |
2391 | */ |
2392 | prepare_tagpreview(true); |
2393 | } |
2394 | } |
2395 | |
2396 | // If it was a CTRL-W CTRL-] command split window now. For ":tab tag" |
2397 | // open a new tab page. |
2398 | if (postponed_split && (swb_flags & (SWB_USEOPEN | SWB_USETAB))) { |
2399 | buf_T *const existing_buf = buflist_findname_exp(fname); |
2400 | |
2401 | if (existing_buf != NULL) { |
2402 | const win_T *wp = NULL; |
2403 | |
2404 | if (swb_flags & SWB_USEOPEN) { |
2405 | wp = buf_jump_open_win(existing_buf); |
2406 | } |
2407 | |
2408 | // If 'switchbuf' contains "usetab": jump to first window in any tab |
2409 | // page containing "existing_buf" if one exists |
2410 | if (wp == NULL && (swb_flags & SWB_USETAB)) { |
2411 | wp = buf_jump_open_tab(existing_buf); |
2412 | } |
2413 | |
2414 | // We've switched to the buffer, the usual loading of the file must |
2415 | // be skipped. |
2416 | if (wp != NULL) { |
2417 | getfile_result = GETFILE_SAME_FILE; |
2418 | } |
2419 | } |
2420 | } |
2421 | if (getfile_result == GETFILE_UNUSED |
2422 | && (postponed_split || cmdmod.tab != 0)) { |
2423 | if (win_split(postponed_split > 0 ? postponed_split : 0, |
2424 | postponed_split_flags) == FAIL) { |
2425 | RedrawingDisabled--; |
2426 | goto erret; |
2427 | } |
2428 | RESET_BINDING(curwin); |
2429 | } |
2430 | |
2431 | if (keep_help) { |
2432 | /* A :ta from a help file will keep the b_help flag set. For ":ptag" |
2433 | * we need to use the flag from the window where we came from. */ |
2434 | if (l_g_do_tagpreview != 0) |
2435 | keep_help_flag = curwin_save->w_buffer->b_help; |
2436 | else |
2437 | keep_help_flag = curbuf->b_help; |
2438 | } |
2439 | |
2440 | if (getfile_result == GETFILE_UNUSED) { |
2441 | // Careful: getfile() may trigger autocommands and call jumpto_tag() |
2442 | // recursively. |
2443 | getfile_result = getfile(0, fname, NULL, true, (linenr_T)0, forceit); |
2444 | } |
2445 | keep_help_flag = false; |
2446 | |
2447 | if (GETFILE_SUCCESS(getfile_result)) { // got to the right file |
2448 | curwin->w_set_curswant = true; |
2449 | postponed_split = 0; |
2450 | |
2451 | save_secure = secure; |
2452 | secure = 1; |
2453 | ++sandbox; |
2454 | save_magic = p_magic; |
2455 | p_magic = false; // always execute with 'nomagic' |
2456 | // Save value of no_hlsearch, jumping to a tag is not a real search |
2457 | const bool save_no_hlsearch = no_hlsearch; |
2458 | |
2459 | /* |
2460 | * If 'cpoptions' contains 't', store the search pattern for the "n" |
2461 | * command. If 'cpoptions' does not contain 't', the search pattern |
2462 | * is not stored. |
2463 | */ |
2464 | if (vim_strchr(p_cpo, CPO_TAGPAT) != NULL) |
2465 | search_options = 0; |
2466 | else |
2467 | search_options = SEARCH_KEEP; |
2468 | |
2469 | /* |
2470 | * If the command is a search, try here. |
2471 | * |
2472 | * Reset 'smartcase' for the search, since the search pattern was not |
2473 | * typed by the user. |
2474 | * Only use do_search() when there is a full search command, without |
2475 | * anything following. |
2476 | */ |
2477 | str = pbuf; |
2478 | if (pbuf[0] == '/' || pbuf[0] == '?') |
2479 | str = skip_regexp(pbuf + 1, pbuf[0], FALSE, NULL) + 1; |
2480 | if (str > pbuf_end - 1) { /* search command with nothing following */ |
2481 | save_p_ws = p_ws; |
2482 | save_p_ic = p_ic; |
2483 | save_p_scs = p_scs; |
2484 | p_ws = true; /* need 'wrapscan' for backward searches */ |
2485 | p_ic = FALSE; /* don't ignore case now */ |
2486 | p_scs = FALSE; |
2487 | save_lnum = curwin->w_cursor.lnum; |
2488 | curwin->w_cursor.lnum = 0; /* start search before first line */ |
2489 | if (do_search(NULL, pbuf[0], pbuf + 1, (long)1, |
2490 | search_options, NULL, NULL)) { |
2491 | retval = OK; |
2492 | } else { |
2493 | int found = 1; |
2494 | int cc; |
2495 | |
2496 | /* |
2497 | * try again, ignore case now |
2498 | */ |
2499 | p_ic = TRUE; |
2500 | if (!do_search(NULL, pbuf[0], pbuf + 1, (long)1, |
2501 | search_options, NULL, NULL)) { |
2502 | // Failed to find pattern, take a guess: "^func (" |
2503 | found = 2; |
2504 | (void)test_for_static(&tagp); |
2505 | cc = *tagp.tagname_end; |
2506 | *tagp.tagname_end = NUL; |
2507 | snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(" , tagp.tagname); |
2508 | if (!do_search(NULL, '/', pbuf, (long)1, |
2509 | search_options, NULL, NULL)) { |
2510 | // Guess again: "^char * \<func (" |
2511 | snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(" , |
2512 | tagp.tagname); |
2513 | if (!do_search(NULL, '/', pbuf, (long)1, |
2514 | search_options, NULL, NULL)) { |
2515 | found = 0; |
2516 | } |
2517 | } |
2518 | *tagp.tagname_end = cc; |
2519 | } |
2520 | if (found == 0) { |
2521 | EMSG(_("E434: Can't find tag pattern" )); |
2522 | curwin->w_cursor.lnum = save_lnum; |
2523 | } else { |
2524 | /* |
2525 | * Only give a message when really guessed, not when 'ic' |
2526 | * is set and match found while ignoring case. |
2527 | */ |
2528 | if (found == 2 || !save_p_ic) { |
2529 | MSG(_("E435: Couldn't find tag, just guessing!" )); |
2530 | if (!msg_scrolled && msg_silent == 0) { |
2531 | ui_flush(); |
2532 | os_delay(1000L, true); |
2533 | } |
2534 | } |
2535 | retval = OK; |
2536 | } |
2537 | } |
2538 | p_ws = save_p_ws; |
2539 | p_ic = save_p_ic; // -V519 |
2540 | p_scs = save_p_scs; |
2541 | |
2542 | /* A search command may have positioned the cursor beyond the end |
2543 | * of the line. May need to correct that here. */ |
2544 | check_cursor(); |
2545 | } else { |
2546 | curwin->w_cursor.lnum = 1; /* start command in line 1 */ |
2547 | do_cmdline_cmd((char *)pbuf); |
2548 | retval = OK; |
2549 | } |
2550 | |
2551 | /* |
2552 | * When the command has done something that is not allowed make sure |
2553 | * the error message can be seen. |
2554 | */ |
2555 | if (secure == 2) |
2556 | wait_return(TRUE); |
2557 | secure = save_secure; |
2558 | p_magic = save_magic; |
2559 | --sandbox; |
2560 | /* restore no_hlsearch when keeping the old search pattern */ |
2561 | if (search_options) { |
2562 | set_no_hlsearch(save_no_hlsearch); |
2563 | } |
2564 | |
2565 | // Return OK if jumped to another file (at least we found the file!). |
2566 | if (getfile_result == GETFILE_OPEN_OTHER) { |
2567 | retval = OK; |
2568 | } |
2569 | |
2570 | if (retval == OK) { |
2571 | /* |
2572 | * For a help buffer: Put the cursor line at the top of the window, |
2573 | * the help subject will be below it. |
2574 | */ |
2575 | if (curbuf->b_help) |
2576 | set_topline(curwin, curwin->w_cursor.lnum); |
2577 | if ((fdo_flags & FDO_TAG) && old_KeyTyped) |
2578 | foldOpenCursor(); |
2579 | } |
2580 | |
2581 | if (l_g_do_tagpreview != 0 |
2582 | && curwin != curwin_save && win_valid(curwin_save)) { |
2583 | /* Return cursor to where we were */ |
2584 | validate_cursor(); |
2585 | redraw_later(VALID); |
2586 | win_enter(curwin_save, true); |
2587 | } |
2588 | |
2589 | RedrawingDisabled--; |
2590 | } else { |
2591 | RedrawingDisabled--; |
2592 | if (postponed_split) { // close the window |
2593 | win_close(curwin, false); |
2594 | postponed_split = 0; |
2595 | } |
2596 | } |
2597 | |
2598 | erret: |
2599 | g_do_tagpreview = 0; // For next time |
2600 | xfree(lbuf); |
2601 | xfree(pbuf); |
2602 | xfree(tofree_fname); |
2603 | xfree(full_fname); |
2604 | |
2605 | return retval; |
2606 | } |
2607 | |
2608 | // If "expand" is true, expand wildcards in fname. |
2609 | // If 'tagrelative' option set, change fname (name of file containing tag) |
2610 | // according to tag_fname (name of tag file containing fname). |
2611 | // Returns a pointer to allocated memory. |
2612 | static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, |
2613 | const bool expand) |
2614 | { |
2615 | char_u *p; |
2616 | char_u *expanded_fname = NULL; |
2617 | expand_T xpc; |
2618 | |
2619 | /* |
2620 | * Expand file name (for environment variables) when needed. |
2621 | */ |
2622 | if (expand && path_has_wildcard(fname)) { |
2623 | ExpandInit(&xpc); |
2624 | xpc.xp_context = EXPAND_FILES; |
2625 | expanded_fname = ExpandOne(&xpc, fname, NULL, |
2626 | WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); |
2627 | if (expanded_fname != NULL) |
2628 | fname = expanded_fname; |
2629 | } |
2630 | |
2631 | char_u *retval; |
2632 | if ((p_tr || curbuf->b_help) |
2633 | && !vim_isAbsName(fname) |
2634 | && (p = path_tail(tag_fname)) != tag_fname) { |
2635 | retval = xmalloc(MAXPATHL); |
2636 | STRCPY(retval, tag_fname); |
2637 | STRLCPY(retval + (p - tag_fname), fname, |
2638 | MAXPATHL - (p - tag_fname)); |
2639 | /* |
2640 | * Translate names like "src/a/../b/file.c" into "src/b/file.c". |
2641 | */ |
2642 | simplify_filename(retval); |
2643 | } else |
2644 | retval = vim_strsave(fname); |
2645 | |
2646 | xfree(expanded_fname); |
2647 | |
2648 | return retval; |
2649 | } |
2650 | |
2651 | /* |
2652 | * Check if we have a tag for the buffer with name "buf_ffname". |
2653 | * This is a bit slow, because of the full path compare in path_full_compare(). |
2654 | * Return TRUE if tag for file "fname" if tag file "tag_fname" is for current |
2655 | * file. |
2656 | */ |
2657 | static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname, char_u *buf_ffname) |
2658 | { |
2659 | int c; |
2660 | int retval = FALSE; |
2661 | char_u *fullname; |
2662 | |
2663 | if (buf_ffname != NULL) { /* if the buffer has a name */ |
2664 | { |
2665 | c = *fname_end; |
2666 | *fname_end = NUL; |
2667 | } |
2668 | fullname = expand_tag_fname(fname, tag_fname, true); |
2669 | retval = (path_full_compare(fullname, buf_ffname, true) & kEqualFiles); |
2670 | xfree(fullname); |
2671 | *fname_end = c; |
2672 | } |
2673 | |
2674 | return retval; |
2675 | } |
2676 | |
2677 | /* |
2678 | * Find the end of the tagaddress. |
2679 | * Return OK if ";\"" is following, FAIL otherwise. |
2680 | */ |
2681 | static int (char_u **pp) |
2682 | { |
2683 | char_u *str = *pp; |
2684 | |
2685 | // Repeat for addresses separated with ';' |
2686 | for (;; ) { |
2687 | if (ascii_isdigit(*str)) { |
2688 | str = skipdigits(str); |
2689 | } else if (*str == '/' || *str == '?') { |
2690 | str = skip_regexp(str + 1, *str, false, NULL); |
2691 | if (*str != **pp) { |
2692 | str = NULL; |
2693 | } else { |
2694 | str++; |
2695 | } |
2696 | } else { |
2697 | // not a line number or search string, look for terminator. |
2698 | str = (char_u *)strstr((char *)str, "|;\"" ); |
2699 | if (str != NULL) { |
2700 | str++; |
2701 | break; |
2702 | } |
2703 | } |
2704 | if (str == NULL || *str != ';' |
2705 | || !(ascii_isdigit(str[1]) || str[1] == '/' || str[1] == '?')) { |
2706 | break; |
2707 | } |
2708 | str++; // skip ';' |
2709 | } |
2710 | |
2711 | if (str != NULL && STRNCMP(str, ";\"" , 2) == 0) { |
2712 | *pp = str; |
2713 | return OK; |
2714 | } |
2715 | return FAIL; |
2716 | } |
2717 | |
2718 | int |
2719 | expand_tags ( |
2720 | int tagnames, /* expand tag names */ |
2721 | char_u *pat, |
2722 | int *num_file, |
2723 | char_u ***file |
2724 | ) |
2725 | { |
2726 | int i; |
2727 | int c; |
2728 | int tagnmflag; |
2729 | char_u tagnm[100]; |
2730 | tagptrs_T t_p; |
2731 | int ret; |
2732 | |
2733 | if (tagnames) |
2734 | tagnmflag = TAG_NAMES; |
2735 | else |
2736 | tagnmflag = 0; |
2737 | if (pat[0] == '/') |
2738 | ret = find_tags(pat + 1, num_file, file, |
2739 | TAG_REGEXP | tagnmflag | TAG_VERBOSE, |
2740 | TAG_MANY, curbuf->b_ffname); |
2741 | else |
2742 | ret = find_tags(pat, num_file, file, |
2743 | TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NOIC, |
2744 | TAG_MANY, curbuf->b_ffname); |
2745 | if (ret == OK && !tagnames) { |
2746 | /* Reorganize the tags for display and matching as strings of: |
2747 | * "<tagname>\0<kind>\0<filename>\0" |
2748 | */ |
2749 | for (i = 0; i < *num_file; i++) { |
2750 | parse_match((*file)[i], &t_p); |
2751 | c = (int)(t_p.tagname_end - t_p.tagname); |
2752 | memmove(tagnm, t_p.tagname, (size_t)c); |
2753 | tagnm[c++] = 0; |
2754 | tagnm[c++] = (t_p.tagkind != NULL && *t_p.tagkind) |
2755 | ? *t_p.tagkind : 'f'; |
2756 | tagnm[c++] = 0; |
2757 | memmove((*file)[i] + c, t_p.fname, t_p.fname_end - t_p.fname); |
2758 | (*file)[i][c + (t_p.fname_end - t_p.fname)] = 0; |
2759 | memmove((*file)[i], tagnm, (size_t)c); |
2760 | } |
2761 | } |
2762 | return ret; |
2763 | } |
2764 | |
2765 | |
2766 | /* |
2767 | * Add a tag field to the dictionary "dict". |
2768 | * Return OK or FAIL. |
2769 | */ |
2770 | static int |
2771 | add_tag_field ( |
2772 | dict_T *dict, |
2773 | const char *field_name, |
2774 | const char_u *start, // start of the value |
2775 | const char_u *end // after the value; can be NULL |
2776 | ) |
2777 | FUNC_ATTR_NONNULL_ARG(1, 2) |
2778 | { |
2779 | int len = 0; |
2780 | int retval; |
2781 | |
2782 | // Check that the field name doesn't exist yet. |
2783 | if (tv_dict_find(dict, field_name, -1) != NULL) { |
2784 | if (p_verbose > 0) { |
2785 | verbose_enter(); |
2786 | smsg(_("Duplicate field name: %s" ), field_name); |
2787 | verbose_leave(); |
2788 | } |
2789 | return FAIL; |
2790 | } |
2791 | char_u *buf = xmalloc(MAXPATHL); |
2792 | if (start != NULL) { |
2793 | if (end == NULL) { |
2794 | end = start + STRLEN(start); |
2795 | while (end > start && (end[-1] == '\r' || end[-1] == '\n')) |
2796 | --end; |
2797 | } |
2798 | len = (int)(end - start); |
2799 | if (len > MAXPATHL - 1) |
2800 | len = MAXPATHL - 1; |
2801 | STRLCPY(buf, start, len + 1); |
2802 | } |
2803 | buf[len] = NUL; |
2804 | retval = tv_dict_add_str(dict, field_name, STRLEN(field_name), |
2805 | (const char *)buf); |
2806 | xfree(buf); |
2807 | return retval; |
2808 | } |
2809 | |
2810 | /// Add the tags matching the specified pattern "pat" to the list "list" |
2811 | /// as a dictionary. Use "buf_fname" for priority, unless NULL. |
2812 | int get_tags(list_T *list, char_u *pat, char_u *buf_fname) |
2813 | { |
2814 | int num_matches, i, ret; |
2815 | char_u **matches; |
2816 | char_u *full_fname; |
2817 | dict_T *dict; |
2818 | tagptrs_T tp; |
2819 | bool is_static; |
2820 | |
2821 | ret = find_tags(pat, &num_matches, &matches, |
2822 | TAG_REGEXP | TAG_NOIC, (int)MAXCOL, buf_fname); |
2823 | if (ret == OK && num_matches > 0) { |
2824 | for (i = 0; i < num_matches; ++i) { |
2825 | int parse_result = parse_match(matches[i], &tp); |
2826 | |
2827 | // Avoid an unused variable warning in release builds. |
2828 | (void) parse_result; |
2829 | assert(parse_result == OK); |
2830 | |
2831 | is_static = test_for_static(&tp); |
2832 | |
2833 | /* Skip pseudo-tag lines. */ |
2834 | if (STRNCMP(tp.tagname, "!_TAG_" , 6) == 0) |
2835 | continue; |
2836 | |
2837 | dict = tv_dict_alloc(); |
2838 | tv_list_append_dict(list, dict); |
2839 | |
2840 | full_fname = tag_full_fname(&tp); |
2841 | if (add_tag_field(dict, "name" , tp.tagname, tp.tagname_end) == FAIL |
2842 | || add_tag_field(dict, "filename" , full_fname, NULL) == FAIL |
2843 | || add_tag_field(dict, "cmd" , tp.command, tp.command_end) == FAIL |
2844 | || add_tag_field(dict, "kind" , tp.tagkind, |
2845 | tp.tagkind ? tp.tagkind_end : NULL) == FAIL |
2846 | || tv_dict_add_nr(dict, S_LEN("static" ), is_static) == FAIL) { |
2847 | ret = FAIL; |
2848 | } |
2849 | |
2850 | xfree(full_fname); |
2851 | |
2852 | if (tp.command_end != NULL) { |
2853 | for (char_u *p = tp.command_end + 3; |
2854 | *p != NUL && *p != '\n' && *p != '\r'; p++) { |
2855 | if (p == tp.tagkind |
2856 | || (p + 5 == tp.tagkind && STRNCMP(p, "kind:" , 5) == 0)) { |
2857 | // skip "kind:<kind>" and "<kind>" |
2858 | p = tp.tagkind_end - 1; |
2859 | } else if (STRNCMP(p, "file:" , 5) == 0) { |
2860 | // skip "file:" (static tag) |
2861 | p += 4; |
2862 | } else if (!ascii_iswhite(*p)) { |
2863 | char_u *s, *n; |
2864 | int len; |
2865 | |
2866 | /* Add extra field as a dict entry. Fields are |
2867 | * separated by Tabs. */ |
2868 | n = p; |
2869 | while (*p != NUL && *p >= ' ' && *p < 127 && *p != ':') |
2870 | ++p; |
2871 | len = (int)(p - n); |
2872 | if (*p == ':' && len > 0) { |
2873 | s = ++p; |
2874 | while (*p != NUL && *p >= ' ') |
2875 | ++p; |
2876 | n[len] = NUL; |
2877 | if (add_tag_field(dict, (char *)n, s, p) == FAIL) |
2878 | ret = FAIL; |
2879 | n[len] = ':'; |
2880 | } else |
2881 | /* Skip field without colon. */ |
2882 | while (*p != NUL && *p >= ' ') |
2883 | ++p; |
2884 | if (*p == NUL) |
2885 | break; |
2886 | } |
2887 | } |
2888 | } |
2889 | |
2890 | xfree(matches[i]); |
2891 | } |
2892 | xfree(matches); |
2893 | } |
2894 | return ret; |
2895 | } |
2896 | |
2897 | // Return information about 'tag' in dict 'retdict'. |
2898 | static void get_tag_details(taggy_T *tag, dict_T *retdict) |
2899 | { |
2900 | list_T *pos; |
2901 | fmark_T *fmark; |
2902 | |
2903 | tv_dict_add_str(retdict, S_LEN("tagname" ), (const char *)tag->tagname); |
2904 | tv_dict_add_nr(retdict, S_LEN("matchnr" ), tag->cur_match + 1); |
2905 | tv_dict_add_nr(retdict, S_LEN("bufnr" ), tag->cur_fnum); |
2906 | |
2907 | pos = tv_list_alloc(4); |
2908 | tv_dict_add_list(retdict, S_LEN("from" ), pos); |
2909 | |
2910 | fmark = &tag->fmark; |
2911 | tv_list_append_number(pos, |
2912 | (varnumber_T)(fmark->fnum != -1 ? fmark->fnum : 0)); |
2913 | tv_list_append_number(pos, (varnumber_T)fmark->mark.lnum); |
2914 | tv_list_append_number(pos, (varnumber_T)(fmark->mark.col == MAXCOL |
2915 | ? MAXCOL : fmark->mark.col + 1)); |
2916 | tv_list_append_number(pos, (varnumber_T)fmark->mark.coladd); |
2917 | } |
2918 | |
2919 | // Return the tag stack entries of the specified window 'wp' in dictionary |
2920 | // 'retdict'. |
2921 | void get_tagstack(win_T *wp, dict_T *retdict) |
2922 | { |
2923 | list_T *l; |
2924 | int i; |
2925 | dict_T *d; |
2926 | |
2927 | tv_dict_add_nr(retdict, S_LEN("length" ), wp->w_tagstacklen); |
2928 | tv_dict_add_nr(retdict, S_LEN("curidx" ), wp->w_tagstackidx + 1); |
2929 | l = tv_list_alloc(2); |
2930 | tv_dict_add_list(retdict, S_LEN("items" ), l); |
2931 | |
2932 | for (i = 0; i < wp->w_tagstacklen; i++) { |
2933 | d = tv_dict_alloc(); |
2934 | tv_list_append_dict(l, d); |
2935 | get_tag_details(&wp->w_tagstack[i], d); |
2936 | } |
2937 | } |
2938 | |
2939 | // Free all the entries in the tag stack of the specified window |
2940 | static void tagstack_clear(win_T *wp) |
2941 | { |
2942 | // Free the current tag stack |
2943 | for (int i = 0; i < wp->w_tagstacklen; i++) { |
2944 | xfree(wp->w_tagstack[i].tagname); |
2945 | } |
2946 | wp->w_tagstacklen = 0; |
2947 | wp->w_tagstackidx = 0; |
2948 | } |
2949 | |
2950 | // Remove the oldest entry from the tag stack and shift the rest of |
2951 | // the entires to free up the top of the stack. |
2952 | static void tagstack_shift(win_T *wp) |
2953 | { |
2954 | taggy_T *tagstack = wp->w_tagstack; |
2955 | xfree(tagstack[0].tagname); |
2956 | for (int i = 1; i < wp->w_tagstacklen; i++) { |
2957 | tagstack[i - 1] = tagstack[i]; |
2958 | } |
2959 | wp->w_tagstacklen--; |
2960 | } |
2961 | |
2962 | // Push a new item to the tag stack |
2963 | static void tagstack_push_item( |
2964 | win_T *wp, |
2965 | char_u *tagname, |
2966 | int cur_fnum, |
2967 | int cur_match, |
2968 | pos_T mark, |
2969 | int fnum) |
2970 | { |
2971 | taggy_T *tagstack = wp->w_tagstack; |
2972 | int idx = wp->w_tagstacklen; // top of the stack |
2973 | |
2974 | // if the tagstack is full: remove the oldest entry |
2975 | if (idx >= TAGSTACKSIZE) { |
2976 | tagstack_shift(wp); |
2977 | idx = TAGSTACKSIZE - 1; |
2978 | } |
2979 | |
2980 | wp->w_tagstacklen++; |
2981 | tagstack[idx].tagname = tagname; |
2982 | tagstack[idx].cur_fnum = cur_fnum; |
2983 | tagstack[idx].cur_match = cur_match; |
2984 | if (tagstack[idx].cur_match < 0) { |
2985 | tagstack[idx].cur_match = 0; |
2986 | } |
2987 | tagstack[idx].fmark.mark = mark; |
2988 | tagstack[idx].fmark.fnum = fnum; |
2989 | } |
2990 | |
2991 | // Add a list of items to the tag stack in the specified window |
2992 | static void tagstack_push_items(win_T *wp, list_T *l) |
2993 | { |
2994 | listitem_T *li; |
2995 | dictitem_T *di; |
2996 | dict_T *itemdict; |
2997 | char_u *tagname; |
2998 | pos_T mark; |
2999 | int fnum; |
3000 | |
3001 | // Add one entry at a time to the tag stack |
3002 | for (li = tv_list_first(l); li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { |
3003 | if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT |
3004 | || TV_LIST_ITEM_TV(li)->vval.v_dict == NULL) { |
3005 | continue; // Skip non-dict items |
3006 | } |
3007 | itemdict = TV_LIST_ITEM_TV(li)->vval.v_dict; |
3008 | |
3009 | // parse 'from' for the cursor position before the tag jump |
3010 | if ((di = tv_dict_find(itemdict, "from" , -1)) == NULL) { |
3011 | continue; |
3012 | } |
3013 | if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) { |
3014 | continue; |
3015 | } |
3016 | if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname" , true)) |
3017 | == NULL) { |
3018 | continue; |
3019 | } |
3020 | |
3021 | if (mark.col > 0) { |
3022 | mark.col--; |
3023 | } |
3024 | tagstack_push_item(wp, tagname, |
3025 | (int)tv_dict_get_number(itemdict, "bufnr" ), |
3026 | (int)tv_dict_get_number(itemdict, "matchnr" ) - 1, |
3027 | mark, fnum); |
3028 | } |
3029 | } |
3030 | |
3031 | // Set the current index in the tag stack. Valid values are between 0 |
3032 | // and the stack length (inclusive). |
3033 | static void tagstack_set_curidx(win_T *wp, int curidx) |
3034 | { |
3035 | wp->w_tagstackidx = curidx; |
3036 | if (wp->w_tagstackidx < 0) { // sanity check |
3037 | wp->w_tagstackidx = 0; |
3038 | } |
3039 | if (wp->w_tagstackidx > wp->w_tagstacklen) { |
3040 | wp->w_tagstackidx = wp->w_tagstacklen; |
3041 | } |
3042 | } |
3043 | |
3044 | // Set the tag stack entries of the specified window. |
3045 | // 'action' is set to either 'a' for append or 'r' for replace. |
3046 | int set_tagstack(win_T *wp, dict_T *d, int action) |
3047 | { |
3048 | dictitem_T *di; |
3049 | list_T *l; |
3050 | |
3051 | if ((di = tv_dict_find(d, "items" , -1)) != NULL) { |
3052 | if (di->di_tv.v_type != VAR_LIST) { |
3053 | return FAIL; |
3054 | } |
3055 | l = di->di_tv.vval.v_list; |
3056 | |
3057 | if (action == 'r') { |
3058 | tagstack_clear(wp); |
3059 | } |
3060 | |
3061 | tagstack_push_items(wp, l); |
3062 | } |
3063 | |
3064 | if ((di = tv_dict_find(d, "curidx" , -1)) != NULL) { |
3065 | tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1); |
3066 | } |
3067 | |
3068 | return OK; |
3069 | } |
3070 | |