GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mg/tags.c Lines: 0 231 0.0 %
Date: 2017-11-07 Branches: 0 306 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: tags.c,v 1.16 2017/08/06 04:39:45 bcallah Exp $	*/
2
3
/*
4
 * This file is in the public domain.
5
 *
6
 * Author: Sunil Nimmagadda <sunil@openbsd.org>
7
 */
8
9
#include <sys/queue.h>
10
#include <sys/stat.h>
11
#include <sys/tree.h>
12
#include <sys/types.h>
13
#include <ctype.h>
14
#include <err.h>
15
#include <errno.h>
16
#include <signal.h>
17
#include <stdio.h>
18
#include <stdlib.h>
19
#include <string.h>
20
#include <unistd.h>
21
#include <util.h>
22
23
#include "def.h"
24
25
struct ctag;
26
27
static int               addctag(char *);
28
static int               atbow(void);
29
void                     closetags(void);
30
static int               ctagcmp(struct ctag *, struct ctag *);
31
static int               loadbuffer(char *);
32
static int               loadtags(const char *);
33
static int               pushtag(char *);
34
static int               searchpat(char *);
35
static struct ctag       *searchtag(char *);
36
static char              *strip(char *, size_t);
37
static void              unloadtags(void);
38
39
#define DEFAULTFN "tags"
40
41
char *tagsfn = NULL;
42
int  loaded  = FALSE;
43
44
/* ctags(1) entries are parsed and maintained in a tree. */
45
struct ctag {
46
	RB_ENTRY(ctag) entry;
47
	char *tag;
48
	char *fname;
49
	char *pat;
50
};
51
RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags);
52
RB_GENERATE(tagtree, ctag, entry, ctagcmp);
53
54
struct tagpos {
55
	SLIST_ENTRY(tagpos) entry;
56
	int    doto;
57
	int    dotline;
58
	char   *bname;
59
};
60
SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead);
61
62
int
63
ctagcmp(struct ctag *s, struct ctag *t)
64
{
65
	return strcmp(s->tag, t->tag);
66
}
67
68
/*
69
 * Record the filename that contain tags to be used while loading them
70
 * on first use. If a filename is already recorded, ask user to retain
71
 * already loaded tags (if any) and unload them if user chooses not to.
72
 */
73
/* ARGSUSED */
74
int
75
tagsvisit(int f, int n)
76
{
77
	char fname[NFILEN], *bufp, *temp;
78
	struct stat sb;
79
80
	if (getbufcwd(fname, sizeof(fname)) == FALSE)
81
		fname[0] = '\0';
82
83
	if (strlcat(fname, DEFAULTFN, sizeof(fname)) >= sizeof(fname)) {
84
		dobeep();
85
		ewprintf("Filename too long");
86
		return (FALSE);
87
	}
88
89
	bufp = eread("Visit tags table (default %s): ", fname,
90
	    NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN);
91
	if (bufp == NULL)
92
		return (ABORT);
93
94
	if (stat(bufp, &sb) == -1) {
95
		dobeep();
96
		ewprintf("stat: %s", strerror(errno));
97
		return (FALSE);
98
	} else if (S_ISREG(sb.st_mode) == 0) {
99
		dobeep();
100
		ewprintf("Not a regular file");
101
		return (FALSE);
102
	} else if (access(bufp, R_OK) == -1) {
103
		dobeep();
104
		ewprintf("Cannot access file %s", bufp);
105
		return (FALSE);
106
	}
107
108
	if (tagsfn == NULL) {
109
		if (bufp[0] == '\0') {
110
			if ((tagsfn = strdup(fname)) == NULL) {
111
				dobeep();
112
				ewprintf("Out of memory");
113
				return (FALSE);
114
			}
115
		} else {
116
			/* bufp points to local variable, so duplicate. */
117
			if ((tagsfn = strdup(bufp)) == NULL) {
118
				dobeep();
119
				ewprintf("Out of memory");
120
				return (FALSE);
121
			}
122
		}
123
	} else {
124
		if ((temp = strdup(bufp)) == NULL) {
125
			dobeep();
126
			ewprintf("Out of memory");
127
			return (FALSE);
128
		}
129
		free(tagsfn);
130
		tagsfn = temp;
131
		if (eyorn("Keep current list of tags table also") == FALSE) {
132
			ewprintf("Starting a new list of tags table");
133
			unloadtags();
134
		}
135
		loaded = FALSE;
136
	}
137
	return (TRUE);
138
}
139
140
/*
141
 * Ask user for a tag while treating word at dot as default. Visit tags
142
 * file if not yet done, load tags and jump to definition of the tag.
143
 */
144
int
145
findtag(int f, int n)
146
{
147
	char utok[MAX_TOKEN], dtok[MAX_TOKEN];
148
	char *tok, *bufp;
149
	int  ret;
150
151
	if (curtoken(f, n, dtok) == FALSE) {
152
		dtok[0] = '\0';
153
		bufp = eread("Find tag: ", utok, MAX_TOKEN, EFNUL | EFNEW);
154
	} else
155
		bufp = eread("Find tag (default %s): ", utok, MAX_TOKEN,
156
		    EFNUL | EFNEW, dtok);
157
158
	if (bufp == NULL)
159
		return (ABORT);
160
	else if	(bufp[0] == '\0')
161
		tok = dtok;
162
	else
163
		tok = utok;
164
165
	if (tok[0] == '\0') {
166
		dobeep();
167
		ewprintf("There is no default tag");
168
		return (FALSE);
169
	}
170
171
	if (tagsfn == NULL)
172
		if ((ret = tagsvisit(f, n)) != TRUE)
173
			return (ret);
174
	if (!loaded) {
175
		if (loadtags(tagsfn) == FALSE) {
176
			free(tagsfn);
177
			tagsfn = NULL;
178
			return (FALSE);
179
		}
180
		loaded = TRUE;
181
	}
182
	return pushtag(tok);
183
}
184
185
/*
186
 * Free tags tree.
187
 */
188
void
189
unloadtags(void)
190
{
191
	struct ctag *var, *nxt;
192
193
	for (var = RB_MIN(tagtree, &tags); var != NULL; var = nxt) {
194
		nxt = RB_NEXT(tagtree, &tags, var);
195
		RB_REMOVE(tagtree, &tags, var);
196
		/* line parsed with fparseln needs to be freed */
197
		free(var->tag);
198
		free(var);
199
	}
200
}
201
202
/*
203
 * Lookup tag passed in tree and if found, push current location and
204
 * buffername onto stack, load the file with tag definition into a new
205
 * buffer and position dot at the pattern.
206
 */
207
/*ARGSUSED */
208
int
209
pushtag(char *tok)
210
{
211
	struct ctag *res;
212
	struct tagpos *s;
213
	char bname[NFILEN];
214
	int doto, dotline;
215
216
	if ((res = searchtag(tok)) == NULL)
217
		return (FALSE);
218
219
	doto = curwp->w_doto;
220
	dotline = curwp->w_dotline;
221
	/* record absolute filenames. Fixes issues when mg's cwd is not the
222
	 * same as buffer's directory.
223
	 */
224
	if (strlcpy(bname, curbp->b_cwd, sizeof(bname)) >= sizeof(bname)) {
225
		dobeep();
226
		ewprintf("filename too long");
227
		return (FALSE);
228
	}
229
	if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) {
230
		dobeep();
231
		ewprintf("filename too long");
232
		return (FALSE);
233
	}
234
235
	if (loadbuffer(res->fname) == FALSE)
236
		return (FALSE);
237
238
	if (searchpat(res->pat) == TRUE) {
239
		if ((s = malloc(sizeof(struct tagpos))) == NULL) {
240
			dobeep();
241
			ewprintf("Out of memory");
242
			return (FALSE);
243
		}
244
		if ((s->bname = strdup(bname)) == NULL) {
245
			dobeep();
246
			ewprintf("Out of memory");
247
			free(s);
248
			return (FALSE);
249
		}
250
		s->doto = doto;
251
		s->dotline = dotline;
252
		SLIST_INSERT_HEAD(&shead, s, entry);
253
		return (TRUE);
254
	} else {
255
		dobeep();
256
		ewprintf("%s: pattern not found", res->tag);
257
		return (FALSE);
258
	}
259
	/* NOTREACHED */
260
	return (FALSE);
261
}
262
263
/*
264
 * If tag stack is not empty pop stack and jump to recorded buffer, dot.
265
 */
266
/* ARGSUSED */
267
int
268
poptag(int f, int n)
269
{
270
	struct line *dotp;
271
	struct tagpos *s;
272
273
	if (SLIST_EMPTY(&shead)) {
274
		dobeep();
275
		ewprintf("No previous location for find-tag invocation");
276
		return (FALSE);
277
	}
278
	s = SLIST_FIRST(&shead);
279
	SLIST_REMOVE_HEAD(&shead, entry);
280
	if (loadbuffer(s->bname) == FALSE)
281
		return (FALSE);
282
	curwp->w_dotline = s->dotline;
283
	curwp->w_doto = s->doto;
284
285
	/* storing of dotp in tagpos wouldn't work out in cases when
286
	 * that buffer is killed by user(dangling pointer). Explicitly
287
	 * traverse till dotline for correct handling.
288
	 */
289
	dotp = curwp->w_bufp->b_headp;
290
	while (s->dotline--)
291
		dotp = dotp->l_fp;
292
293
	curwp->w_dotp = dotp;
294
	free(s->bname);
295
	free(s);
296
	return (TRUE);
297
}
298
299
/*
300
 * Parse the tags file and construct the tags tree. Remove escape
301
 * characters while parsing the file.
302
 */
303
int
304
loadtags(const char *fn)
305
{
306
	char *l;
307
	FILE *fd;
308
309
	if ((fd = fopen(fn, "r")) == NULL) {
310
		dobeep();
311
		ewprintf("Unable to open tags file: %s", fn);
312
		return (FALSE);
313
	}
314
	while ((l = fparseln(fd, NULL, NULL, "\\\\\0",
315
	    FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) {
316
		if (addctag(l) == FALSE) {
317
			fclose(fd);
318
			return (FALSE);
319
		}
320
	}
321
	fclose(fd);
322
	return (TRUE);
323
}
324
325
/*
326
 * Cleanup and destroy tree and stack.
327
 */
328
void
329
closetags(void)
330
{
331
	struct tagpos *s;
332
333
	while (!SLIST_EMPTY(&shead)) {
334
		s = SLIST_FIRST(&shead);
335
		SLIST_REMOVE_HEAD(&shead, entry);
336
		free(s->bname);
337
		free(s);
338
	}
339
	unloadtags();
340
	free(tagsfn);
341
}
342
343
/*
344
 * Strip away any special characters in pattern.
345
 * The pattern in ctags isn't a true regular expression. Its of the form
346
 * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip
347
 * the leading and trailing special characters so the pattern matching
348
 * would be a simple string compare. Escape character is taken care by
349
 * fparseln.
350
 */
351
char *
352
strip(char *s, size_t len)
353
{
354
	/* first strip trailing special chars */
355
	s[len - 1] = '\0';
356
	if (s[len - 2] == '$')
357
		s[len - 2] = '\0';
358
359
	/* then strip leading special chars */
360
	s++;
361
	if (*s == '^')
362
		s++;
363
364
	return s;
365
}
366
367
/*
368
 * tags line is of the format "<tag>\t<filename>\t<pattern>". Split them
369
 * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed
370
 * l, and can be freed during cleanup.
371
 */
372
int
373
addctag(char *l)
374
{
375
	struct ctag *t;
376
377
	if ((t = malloc(sizeof(struct ctag))) == NULL) {
378
		dobeep();
379
		ewprintf("Out of memory");
380
		return (FALSE);
381
	}
382
	t->tag = l;
383
	if ((l = strchr(l, '\t')) == NULL)
384
		goto cleanup;
385
	*l++ = '\0';
386
	t->fname = l;
387
	if ((l = strchr(l, '\t')) == NULL)
388
		goto cleanup;
389
	*l++ = '\0';
390
	if (*l == '\0')
391
		goto cleanup;
392
	t->pat = strip(l, strlen(l));
393
	RB_INSERT(tagtree, &tags, t);
394
	return (TRUE);
395
cleanup:
396
	free(t);
397
	free(l);
398
	return (FALSE);
399
}
400
401
/*
402
 * Search through each line of buffer for pattern.
403
 */
404
int
405
searchpat(char *s_pat)
406
{
407
	struct line *lp;
408
	int dotline;
409
	size_t plen;
410
411
	plen = strlen(s_pat);
412
	dotline = 1;
413
	lp = lforw(curbp->b_headp);
414
	while (lp != curbp->b_headp) {
415
		if (ltext(lp) != NULL && plen <= llength(lp) &&
416
		    (strncmp(s_pat, ltext(lp), plen) == 0)) {
417
			curwp->w_doto = 0;
418
			curwp->w_dotp = lp;
419
			curwp->w_dotline = dotline;
420
			return (TRUE);
421
		} else {
422
			lp = lforw(lp);
423
			dotline++;
424
		}
425
	}
426
	return (FALSE);
427
}
428
429
/*
430
 * Return TRUE if dot is at beginning of a word or at beginning
431
 * of line, else FALSE.
432
 */
433
int
434
atbow(void)
435
{
436
	if (curwp->w_doto == 0)
437
		return (TRUE);
438
	if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) &&
439
	    !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1]))
440
	    	return (TRUE);
441
	return (FALSE);
442
}
443
444
/*
445
 * Extract the word at dot without changing dot position.
446
 */
447
int
448
curtoken(int f, int n, char *token)
449
{
450
	struct line *odotp;
451
	int odoto, tdoto, odotline, size, r;
452
	char c;
453
454
	/* Underscore character is to be treated as "inword" while
455
	 * processing tokens unlike mg's default word traversal. Save
456
	 * and restore it's cinfo value so that tag matching works for
457
	 * identifier with underscore.
458
	 */
459
	c = cinfo['_'];
460
	cinfo['_'] = _MG_W;
461
462
	odotp = curwp->w_dotp;
463
	odoto = curwp->w_doto;
464
	odotline = curwp->w_dotline;
465
466
	/* Move backword unless we are at the beginning of a word or at
467
	 * beginning of line.
468
	 */
469
	if (!atbow())
470
		if ((r = backword(f, n)) == FALSE)
471
			goto cleanup;
472
473
	tdoto = curwp->w_doto;
474
475
	if ((r = forwword(f, n)) == FALSE)
476
		goto cleanup;
477
478
	/* strip away leading whitespace if any like emacs. */
479
	while (ltext(curwp->w_dotp) &&
480
	    isspace(lgetc(curwp->w_dotp, tdoto)))
481
		tdoto++;
482
483
	size = curwp->w_doto - tdoto;
484
	if (size <= 0 || size >= MAX_TOKEN ||
485
	    ltext(curwp->w_dotp) == NULL) {
486
		r = FALSE;
487
		goto cleanup;
488
	}
489
	strncpy(token, ltext(curwp->w_dotp) + tdoto, size);
490
	token[size] = '\0';
491
	r = TRUE;
492
493
cleanup:
494
	cinfo['_'] = c;
495
	curwp->w_dotp = odotp;
496
	curwp->w_doto = odoto;
497
	curwp->w_dotline = odotline;
498
	return (r);
499
}
500
501
/*
502
 * Search tagstree for a given token.
503
 */
504
struct ctag *
505
searchtag(char *tok)
506
{
507
	struct ctag t, *res;
508
509
	t.tag = tok;
510
	if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
511
		dobeep();
512
		ewprintf("No tag containing %s", tok);
513
		return (NULL);
514
	}
515
	return res;
516
}
517
518
/*
519
 * This is equivalent to filevisit from file.c.
520
 * Look around to see if we can find the file in another buffer; if we
521
 * can't find it, create a new buffer, read in the text, and switch to
522
 * the new buffer. *scratch*, *grep*, *compile* needs to be handled
523
 * differently from other buffers which have "filenames".
524
 */
525
int
526
loadbuffer(char *bname)
527
{
528
	struct buffer *bufp;
529
	char *adjf;
530
531
	/* check for special buffers which begin with '*' */
532
	if (bname[0] == '*') {
533
		if ((bufp = bfind(bname, FALSE)) != NULL) {
534
			curbp = bufp;
535
			return (showbuffer(bufp, curwp, WFFULL));
536
		} else {
537
			return (FALSE);
538
		}
539
	} else {
540
		if ((adjf = adjustname(bname, TRUE)) == NULL)
541
			return (FALSE);
542
		if ((bufp = findbuffer(adjf)) == NULL)
543
			return (FALSE);
544
	}
545
	curbp = bufp;
546
	if (showbuffer(bufp, curwp, WFFULL) != TRUE)
547
		return (FALSE);
548
	if (bufp->b_fname[0] == '\0') {
549
		if (readin(adjf) != TRUE) {
550
			killbuffer(bufp);
551
			return (FALSE);
552
		}
553
	}
554
	return (TRUE);
555
}