tags.c revision 1.16
1/* 2 * Copyright (C) 1984-2012 Mark Nudelman 3 * Modified for use with illumos by Garrett D'Amore. 4 * Copyright 2014 Garrett D'Amore <garrett@damore.org> 5 * 6 * You may distribute under the terms of either the GNU General Public 7 * License or the Less License, as specified in the README file. 8 * 9 * For more information, see the README file. 10 */ 11 12#include "less.h" 13 14#define WHITESP(c) ((c) == ' ' || (c) == '\t') 15 16char *tags = "tags"; 17 18static int total; 19static int curseq; 20 21extern int linenums; 22extern volatile sig_atomic_t sigs; 23 24enum tag_result { 25 TAG_FOUND, 26 TAG_NOFILE, 27 TAG_NOTAG, 28 TAG_NOTYPE, 29 TAG_INTR 30}; 31 32static enum tag_result findctag(char *); 33static char *nextctag(void); 34static char *prevctag(void); 35static off_t ctagsearch(void); 36 37/* 38 * The list of tags generated by the last findctag() call. 39 */ 40struct taglist { 41 struct tag *tl_first; 42 struct tag *tl_last; 43}; 44#define TAG_END ((struct tag *)&taglist) 45static struct taglist taglist = { TAG_END, TAG_END }; 46struct tag { 47 struct tag *next, *prev; /* List links */ 48 char *tag_file; /* Source file containing the tag */ 49 LINENUM tag_linenum; /* Appropriate line number in source file */ 50 char *tag_pattern; /* Pattern used to find the tag */ 51 int tag_endline; /* True if the pattern includes '$' */ 52}; 53static struct tag *curtag; 54 55#define TAG_INS(tp) \ 56 (tp)->next = TAG_END; \ 57 (tp)->prev = taglist.tl_last; \ 58 taglist.tl_last->next = (tp); \ 59 taglist.tl_last = (tp); 60 61#define TAG_RM(tp) \ 62 (tp)->next->prev = (tp)->prev; \ 63 (tp)->prev->next = (tp)->next; 64 65/* 66 * Delete tag structures. 67 */ 68void 69cleantags(void) 70{ 71 struct tag *tp; 72 73 /* 74 * Delete any existing tag list. 75 * {{ Ideally, we wouldn't do this until after we know that we 76 * can load some other tag information. }} 77 */ 78 while ((tp = taglist.tl_first) != TAG_END) { 79 TAG_RM(tp); 80 free(tp); 81 } 82 curtag = NULL; 83 total = curseq = 0; 84} 85 86/* 87 * Create a new tag entry. 88 */ 89static struct tag * 90maketagent(char *file, LINENUM linenum, char *pattern, int endline) 91{ 92 struct tag *tp; 93 94 tp = ecalloc(sizeof (struct tag), 1); 95 tp->tag_file = estrdup(file); 96 tp->tag_linenum = linenum; 97 tp->tag_endline = endline; 98 if (pattern == NULL) 99 tp->tag_pattern = NULL; 100 else 101 tp->tag_pattern = estrdup(pattern); 102 return (tp); 103} 104 105/* 106 * Find tags in tag file. 107 */ 108void 109findtag(char *tag) 110{ 111 enum tag_result result; 112 113 result = findctag(tag); 114 switch (result) { 115 case TAG_FOUND: 116 case TAG_INTR: 117 break; 118 case TAG_NOFILE: 119 error("No tags file", NULL); 120 break; 121 case TAG_NOTAG: 122 error("No such tag in tags file", NULL); 123 break; 124 case TAG_NOTYPE: 125 error("unknown tag type", NULL); 126 break; 127 } 128} 129 130/* 131 * Search for a tag. 132 */ 133off_t 134tagsearch(void) 135{ 136 if (curtag == NULL) 137 return (-1); /* No tags loaded! */ 138 if (curtag->tag_linenum != 0) 139 return (find_pos(curtag->tag_linenum)); 140 return (ctagsearch()); 141} 142 143/* 144 * Go to the next tag. 145 */ 146char * 147nexttag(int n) 148{ 149 char *tagfile = NULL; 150 151 while (n-- > 0) 152 tagfile = nextctag(); 153 return (tagfile); 154} 155 156/* 157 * Go to the previous tag. 158 */ 159char * 160prevtag(int n) 161{ 162 char *tagfile = NULL; 163 164 while (n-- > 0) 165 tagfile = prevctag(); 166 return (tagfile); 167} 168 169/* 170 * Return the total number of tags. 171 */ 172int 173ntags(void) 174{ 175 return (total); 176} 177 178/* 179 * Return the sequence number of current tag. 180 */ 181int 182curr_tag(void) 183{ 184 return (curseq); 185} 186 187/* 188 * Find tags in the "tags" file. 189 * Sets curtag to the first tag entry. 190 */ 191static enum tag_result 192findctag(char *tag) 193{ 194 char *p; 195 FILE *f; 196 int taglen; 197 LINENUM taglinenum; 198 char *tagfile; 199 char *tagpattern; 200 int tagendline; 201 int search_char; 202 int err; 203 char tline[TAGLINE_SIZE]; 204 struct tag *tp; 205 206 p = shell_unquote(tags); 207 f = fopen(p, "r"); 208 free(p); 209 if (f == NULL) 210 return (TAG_NOFILE); 211 212 cleantags(); 213 total = 0; 214 taglen = strlen(tag); 215 216 /* 217 * Search the tags file for the desired tag. 218 */ 219 while (fgets(tline, sizeof (tline), f) != NULL) { 220 if (tline[0] == '!') 221 /* Skip header of extended format. */ 222 continue; 223 if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen])) 224 continue; 225 226 /* 227 * Found it. 228 * The line contains the tag, the filename and the 229 * location in the file, separated by white space. 230 * The location is either a decimal line number, 231 * or a search pattern surrounded by a pair of delimiters. 232 * Parse the line and extract these parts. 233 */ 234 tagpattern = NULL; 235 236 /* 237 * Skip over the whitespace after the tag name. 238 */ 239 p = skipsp(tline+taglen); 240 if (*p == '\0') 241 /* File name is missing! */ 242 continue; 243 244 /* 245 * Save the file name. 246 * Skip over the whitespace after the file name. 247 */ 248 tagfile = p; 249 while (!WHITESP(*p) && *p != '\0') 250 p++; 251 *p++ = '\0'; 252 p = skipsp(p); 253 if (*p == '\0') 254 /* Pattern is missing! */ 255 continue; 256 257 /* 258 * First see if it is a line number. 259 */ 260 tagendline = 0; 261 taglinenum = getnum(&p, 0, &err); 262 if (err) { 263 /* 264 * No, it must be a pattern. 265 * Delete the initial "^" (if present) and 266 * the final "$" from the pattern. 267 * Delete any backslash in the pattern. 268 */ 269 taglinenum = 0; 270 search_char = *p++; 271 if (*p == '^') 272 p++; 273 tagpattern = p; 274 while (*p != search_char && *p != '\0') { 275 if (*p == '\\') 276 p++; 277 p++; 278 } 279 tagendline = (p[-1] == '$'); 280 if (tagendline) 281 p--; 282 *p = '\0'; 283 } 284 tp = maketagent(tagfile, taglinenum, tagpattern, tagendline); 285 TAG_INS(tp); 286 total++; 287 } 288 fclose(f); 289 if (total == 0) 290 return (TAG_NOTAG); 291 curtag = taglist.tl_first; 292 curseq = 1; 293 return (TAG_FOUND); 294} 295 296/* 297 * Edit current tagged file. 298 */ 299int 300edit_tagfile(void) 301{ 302 if (curtag == NULL) 303 return (1); 304 return (edit(curtag->tag_file)); 305} 306 307/* 308 * Search for a tag. 309 * This is a stripped-down version of search(). 310 * We don't use search() for several reasons: 311 * - We don't want to blow away any search string we may have saved. 312 * - The various regular-expression functions (from different systems: 313 * regcmp vs. re_comp) behave differently in the presence of 314 * parentheses (which are almost always found in a tag). 315 */ 316static off_t 317ctagsearch(void) 318{ 319 off_t pos, linepos; 320 LINENUM linenum; 321 int len; 322 char *line; 323 324 pos = ch_zero(); 325 linenum = find_linenum(pos); 326 327 for (;;) { 328 /* 329 * Get lines until we find a matching one or 330 * until we hit end-of-file. 331 */ 332 if (ABORT_SIGS()) 333 return (-1); 334 335 /* 336 * Read the next line, and save the 337 * starting position of that line in linepos. 338 */ 339 linepos = pos; 340 pos = forw_raw_line(pos, &line, (int *)NULL); 341 if (linenum != 0) 342 linenum++; 343 344 if (pos == -1) { 345 /* 346 * We hit EOF without a match. 347 */ 348 error("Tag not found", NULL); 349 return (-1); 350 } 351 352 /* 353 * If we're using line numbers, we might as well 354 * remember the information we have now (the position 355 * and line number of the current line). 356 */ 357 if (linenums) 358 add_lnum(linenum, pos); 359 360 /* 361 * Test the line to see if we have a match. 362 * Use strncmp because the pattern may be 363 * truncated (in the tags file) if it is too long. 364 * If tagendline is set, make sure we match all 365 * the way to end of line (no extra chars after the match). 366 */ 367 len = strlen(curtag->tag_pattern); 368 if (strncmp(curtag->tag_pattern, line, len) == 0 && 369 (!curtag->tag_endline || line[len] == '\0' || 370 line[len] == '\r')) { 371 curtag->tag_linenum = find_linenum(linepos); 372 break; 373 } 374 } 375 376 return (linepos); 377} 378 379static int circular = 0; /* 1: circular tag structure */ 380 381/* 382 * Return the filename required for the next tag in the queue that was setup 383 * by findctag(). The next call to ctagsearch() will try to position at the 384 * appropriate tag. 385 */ 386static char * 387nextctag(void) 388{ 389 struct tag *tp; 390 391 if (curtag == NULL) 392 /* No tag loaded */ 393 return (NULL); 394 395 tp = curtag->next; 396 if (tp == TAG_END) { 397 if (!circular) 398 return (NULL); 399 /* Wrapped around to the head of the queue */ 400 curtag = taglist.tl_first; 401 curseq = 1; 402 } else { 403 curtag = tp; 404 curseq++; 405 } 406 return (curtag->tag_file); 407} 408 409/* 410 * Return the filename required for the previous ctag in the queue that was 411 * setup by findctag(). The next call to ctagsearch() will try to position 412 * at the appropriate tag. 413 */ 414static char * 415prevctag(void) 416{ 417 struct tag *tp; 418 419 if (curtag == NULL) 420 /* No tag loaded */ 421 return (NULL); 422 423 tp = curtag->prev; 424 if (tp == TAG_END) { 425 if (!circular) 426 return (NULL); 427 /* Wrapped around to the tail of the queue */ 428 curtag = taglist.tl_last; 429 curseq = total; 430 } else { 431 curtag = tp; 432 curseq--; 433 } 434 return (curtag->tag_file); 435} 436