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 */
53typedef 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 */
70typedef 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
92static 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 */
96static 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
103static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack");
104static char_u *topmsg = (char_u *)N_("E556: at top of tag stack");
105
106static 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 */
112static 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 */
134int
135do_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
911end_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 */
924void tag_freematch(void)
925{
926 XFREE_CLEAR(tagmatchname);
927}
928
929static 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 */
941void 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 */
982static 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 */
1003static 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 */
1058int
1059find_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 }
1417line_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
1529parse_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 ML_EXTRA 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
1912findtag_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
1965static 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 */
1971static 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)
1983void 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 */
2002int
2003get_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 */
2115void 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 */
2131static int
2132parse_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 */
2179static 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.
2195static 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 */
2214static int
2215parse_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 */
2278static 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 */
2293static 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
2598erret:
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.
2612static 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 */
2657static 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 */
2681static int find_extra(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
2718int
2719expand_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 */
2770static int
2771add_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.
2812int 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'.
2898static 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'.
2921void 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
2940static 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.
2952static 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
2963static 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
2992static 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).
3033static 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.
3046int 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