2
* Copyright © 2007 Paulo César Pereira de Andrade
4
* Permission is hereby granted, free of charge, to any person obtaining a
5
* copy of this software and associated documentation files (the "Software"),
6
* to deal in the Software without restriction, including without limitation
7
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
8
* and/or sell copies of the Software, and to permit persons to whom the
9
* Software is furnished to do so, subject to the following conditions:
11
* The above copyright notice and this permission notice (including the next
12
* paragraph) shall be included in all copies or substantial portions of the
15
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
* DEALINGS IN THE SOFTWARE.
23
* Author: Paulo César Pereira de Andrade
27
* Certain tag files may require quite some time and memory to load.
28
* Linux kernel 2.6x is an example, the tags file itself is almost 80Mb
29
* and xedit will use over 100Mb to store the data, and take quite some
30
* time to load it (and can grow drastically with every loaded files
31
* due to the memory used by the file contents and internal structures,
32
* like the syntax highlight ones).
33
* Possible workarounds could be to load the tags file in a separate
34
* process or thread. The memory problem would be hard to circunvent,
35
* as the tags file metadata would need to be stored in some very fast
36
* database, or at least some special format that would not require
37
* a linear search in a huge tags file.
48
typedef struct _TagsEntry TagsEntry;
49
typedef struct _RegexEntry RegexEntry;
56
hash_entry **filenames;
67
struct _XeditTagsInfo {
72
hash_table *filenames;
75
/* Used when searching for alternate tags and failing descending to
79
/* Flag to know if tags file is in xedit cwd and allow using relative
80
* pathnames when loading a file with some tag definition, so that
81
* other code will not fail to write file (or even worse, write to
82
* wrong file) if file is edited and tags is not in the current dir */
85
/* Cache information for circulating over multiple definitions */
86
XeditTagsInfo *tags; /* If trying another TagsInfo */
87
TagsEntry *entry; /* Entry in tags->tags */
90
XawTextPosition position;
96
static XeditTagsInfo *LoadTagsFile(char *tagsfile);
97
static XeditTagsInfo *DoLoadTagsFile(char *tagsfile, int length);
98
static void FindTagFirst(XeditTagsInfo *tags, char *symbol, int length);
99
static void FindTagNext(XeditTagsInfo *tags,
100
Widget window, XawTextPosition position);
101
static void FindTag(XeditTagsInfo *tags);
106
extern Widget texts[3];
107
static hash_table *ht_tags;
113
TagsAction(Widget w, XEvent *event, String *params, Cardinal *num_params)
115
xedit_flist_item *item;
117
XawTextPosition position, left, right;
122
source = XawTextGetSource(w);
123
item = FindTextSource(source, NULL);
124
if (item->tags == NULL)
125
SearchTagsFile(item);
128
position = XawTextGetInsertionPoint(w);
129
XawTextGetSelectionPos(w, &left, &right);
131
XawTextSourceRead(source, left, &block, right - left);
132
length = block.length + 1;
133
if (length >= sizeof(buffer))
134
length = sizeof(buffer);
135
XmuSnprintf(buffer, length, "%s", block.ptr);
136
item->tags->textwindow = w;
137
item->tags->position = position;
138
FindTagFirst(item->tags, buffer, length - 1);
141
FindTagNext(item->tags, w, position);
148
SearchTagsFile(xedit_flist_item *item)
150
if (app_resources.loadTags) {
152
char *ptr, *tagsfile;
155
FileAccess file_access;
159
/* If path fully specified in resource */
160
if (app_resources.tagsName[0] == '/')
161
tagsfile = ResolveName(app_resources.tagsName);
162
/* Descend up to root directory searching for a tags file */
164
/* *scratch* buffer */
165
if (item->filename[0] != '/') {
166
ptr = ResolveName(app_resources.tagsName);
167
strncpy(buffer, ptr ? ptr : "", sizeof(buffer));
170
strncpy(buffer, item->filename, sizeof(buffer));
172
/* Make sure buffer is nul terminated */
173
buffer[sizeof(buffer) - 1] = '\0';
174
ptr = buffer + strlen(buffer);
177
while (ptr > buffer && ptr[-1] != '/')
181
length = ptr - buffer;
182
if (length >= sizeof(buffer))
183
length = sizeof(buffer);
184
strncpy(ptr, app_resources.tagsName,
185
sizeof(buffer) - length);
186
buffer[sizeof(buffer) - 1] = '\0';
188
/* Check if tags filename exists */
189
tagsfile = ResolveName(buffer);
190
if (tagsfile != NULL) {
191
file_access = CheckFilePermissions(tagsfile, &exists);
192
/* Check if can read tagsfile */
194
(file_access == READ_OK || file_access == WRITE_OK))
204
item->tags = LoadTagsFile(tagsfile);
206
XeditPrintf("No tags file found."
207
" Run \"ctags -R\" to build a tags file.\n");
214
FindTagFirst(XeditTagsInfo *tags, char *symbol, int length)
220
/* Check for malformed parameters */
223
if (*ptr == ' ' || *ptr == '\t' || *ptr == '\n' || *ptr == '\r' ||
224
*ptr == '(' || *ptr == ')') {
231
/* First try in buffer tags */
233
entry = (TagsEntry *)hash_check(tags->entries, symbol, length);
235
/* Try to find in alternate tags */
236
strncpy(buffer, tags->pathname->value, tags->pathname->length);
237
buffer[tags->pathname->length] = '\0';
238
ptr = buffer + tags->pathname->length - 1;
240
for (tags->tags = (XeditTagsInfo *)hash_iter_first(ht_tags);
242
tags->tags = (XeditTagsInfo *)hash_iter_next(ht_tags))
243
tags->tags->visited = False;
245
tags->visited = True;
247
while (ptr > buffer && entry == NULL) {
249
while (ptr > buffer && ptr[-1] != '/')
255
/* Try an upper directory tags */
256
tags->tags = (XeditTagsInfo *)
257
hash_check(ht_tags, buffer, ptr - buffer);
259
tags->tags->visited = True;
260
entry = (TagsEntry *)
261
hash_check(tags->tags->entries, symbol, length);
265
/* If still failed, check other available tags
266
* for possible different projects */
268
for (tags->tags = (XeditTagsInfo *)hash_iter_first(ht_tags);
270
tags->tags = (XeditTagsInfo *)hash_iter_next(ht_tags)) {
271
if (tags->tags->visited == False) {
272
entry = (TagsEntry *)
273
hash_check(tags->tags->entries, symbol, length);
274
/* Stop on first match */
282
XeditPrintf("Symbol %s not in tags\n", symbol);
295
FindTagNext(XeditTagsInfo *tags, Widget window, XawTextPosition position)
297
if (window != tags->textwindow || position != tags->position)
300
if (tags->entry->nentries > 1) {
301
if (++tags->offset >= tags->entry->nentries)
310
static XeditTagsInfo *
311
LoadTagsFile(char *tagsfile)
317
ht_tags = hash_new(11, NULL);
319
/* tags key is only the directory name with ending '/' */
320
length = strlen(tagsfile) - strlen(app_resources.tagsName);
321
tags = (XeditTagsInfo *)hash_check(ht_tags, tagsfile, length);
323
return (tags ? tags : DoLoadTagsFile(tagsfile, length));
326
static XeditTagsInfo *
327
DoLoadTagsFile(char *tagsfile, int length)
333
hash_entry *file_entry;
335
char *symbol, *filename, *pattern;
337
file = fopen(tagsfile, "r");
341
tags = XtNew(XeditTagsInfo);
343
cwd = getcwd(buffer, sizeof(buffer));
345
(strlen(cwd) == length - 1 &&
346
memcmp(cwd, tagsfile, length - 1) == 0);
348
/* Build pathname as a nul terminated directory specification string */
349
tags->pathname = XtNew(hash_key);
350
tags->pathname->value = XtMalloc(length + 1);
351
tags->pathname->length = length;
352
memcpy(tags->pathname->value, tagsfile, length);
353
tags->pathname->value[length] = '\0';
356
tags->entries = hash_new(809, NULL);
357
tags->filenames = hash_new(31, NULL);
358
tags->patterns = hash_new(47, NULL);
360
/* Cache information */
361
tags->tags = tags; /* :-) */
364
tags->textwindow = NULL;
367
while (fgets(buffer, sizeof(buffer) - 1, file)) {
368
/* XXX Ignore malformed lines and tags file format information */
369
if (isspace(buffer[0]) || buffer[0] == '!')
373
symbol = ptr = buffer;
374
while (*ptr && !isspace(*ptr))
377
while (isspace(*ptr))
380
/* Filename with basename of tagsfile for symbol definition */
382
while (*ptr && !isspace(*ptr))
385
while (isspace(*ptr))
389
/* Check for regex */
390
if (*pattern == '/' || *pattern == '?') {
392
while (*ptr && *ptr != *pattern) {
394
if (ptr[1] == *pattern || ptr[1] == '\\') {
395
/* XXX tags will escape pattern end, and backslash
396
* not sure about other special characters */
397
memmove(ptr, ptr + 1, strlen(ptr));
408
if (*ptr != *pattern)
411
/* Will do a RE_NOSPEC search, that means ^ and $
412
* would be literally search (do this to avoid escaping
413
* other regex characters and building a fast/simple literal
414
* string search pattern.
415
* Expect patterns to be full line */
416
if (*pattern == '^' && ptr[-1] == '$') {
421
/* Check for line number */
422
else if (isdigit(*ptr)) {
423
while (isdigit(*ptr))
426
/* Format not understood */
432
length = strlen(symbol);
433
entry = (TagsEntry *)hash_check(tags->entries,
436
entry = XtNew(TagsEntry);
437
entry->symbol = XtNew(hash_key);
438
entry->symbol->value = XtNewString(symbol);
439
entry->symbol->length = length;
442
entry->filenames = NULL;
443
entry->patterns = NULL;
444
hash_put(tags->entries, (hash_entry *)entry);
447
length = strlen(filename);
448
file_entry = hash_check(tags->filenames, filename, length);
449
if (file_entry == NULL) {
450
file_entry = XtNew(hash_entry);
451
file_entry->key = XtNew(hash_key);
452
file_entry->key->value = XtNewString(filename);
453
file_entry->key->length = length;
454
file_entry->next = NULL;
455
hash_put(tags->filenames, file_entry);
458
if ((entry->nentries % 4) == 0) {
459
entry->filenames = (hash_entry **)
460
XtRealloc((char *)entry->filenames,
461
sizeof(hash_entry *) *
462
(entry->nentries + 4));
463
entry->patterns = (char **)
464
XtRealloc((char *)entry->patterns,
466
(entry->nentries + 4));
468
entry->filenames[entry->nentries] = file_entry;
469
entry->patterns[entry->nentries] = XtNewString(pattern);
474
/* Add tags information to global hash table */
475
hash_put(ht_tags, (hash_entry *)tags);
476
XeditPrintf("Tags file %s loaded\n", tagsfile);
479
XeditPrintf("Failed to load tags file %s\n", tagsfile);
487
FindTag(XeditTagsInfo *tags)
489
static String params[] = { "vertical", NULL };
498
XawTextPosition position, left, right, last;
504
xedit_flist_item *item;
507
XmuSnprintf(buffer, sizeof(buffer), "%s%s", tags->tags->pathname->value,
508
tags->entry->filenames[tags->offset]->key->value);
510
pattern = tags->entry->patterns[tags->offset];
511
if (isdigit(*pattern)) {
512
lineno = atoi(pattern);
517
length = strlen(pattern);
518
regex = (RegexEntry *)hash_check(tags->patterns, pattern, length);
520
regex = XtNew(RegexEntry);
521
regex->pattern = XtNew(hash_key);
522
regex->pattern->value = XtNewString(pattern);
523
regex->pattern->length = length;
525
if (recomp(®ex->regex, pattern, RE_NOSUB | RE_NOSPEC)) {
526
XeditPrintf("Failed to compile regex %s\n", pattern);
530
hash_put(tags->patterns, (hash_entry *)regex);
534
/* Short circuit to know if split horizontally */
535
if (!XtIsManaged(texts[1]))
536
XtCallActionProc(textwindow, "split-window", NULL, params, 1);
538
/* Switch to "other" buffer */
539
XtCallActionProc(textwindow, "other-window", NULL, NULL, 0);
541
/* This should print an error message if tags file cannot be read */
542
if (!LoadFileInTextwindow(tags->incwd ?
543
tags->entry->filenames[tags->offset]->key->value :
547
otherwindow = textwindow;
549
item = FindTextSource(XawTextGetSource(textwindow), NULL);
550
source = item->source;
551
left = XawTextSourceScan(source, 0, XawstAll, XawsdLeft, 1, True);
556
right = RSCAN(left, lineno, False);
557
left = LSCAN(right, 1, False);
561
right = RSCAN(left, 1, True);
562
last = XawTextSourceScan(source, 0, XawstAll, XawsdRight, 1, True);
565
size = sizeof(buffer);
567
length = right - left;
569
match.rm_eo = length;
570
XawTextSourceRead(source, left, &block, right - left);
571
if (block.length >= length)
576
text = XtMalloc(length);
578
text = XtRealloc(text, length);
582
memcpy(line, block.ptr, block.length);
583
length = block.length;
584
for (position = left + length;
586
position += block.length) {
587
XawTextSourceRead(source, position, &block, right - position);
588
memcpy(line + length, block.ptr, block.length);
589
length += block.length;
593
/* If not last line or if it ends in a newline */
595
(right > left && line[match.rm_eo - 1] == '\n')) {
597
length = match.rm_eo;
600
/* Accept as a match when matching the entire line, as the regex
601
* search pattern is optmized to not need to start with ^ and not
602
* need to end with $*/
603
if (reexec(®ex->regex, line, 1, &match, RE_STARTEND) == 0 &&
604
match.rm_eo > match.rm_so &&
605
match.rm_so == 0 && match.rm_eo == length) {
606
right = left + match.rm_so + (match.rm_eo - match.rm_so);
610
else if (right >= last) {
611
XeditPrintf("Failed to match regex %s\n", pattern);
616
left = LSCAN(right + 1, 1, False);
617
right = RSCAN(left, 1, True);
625
/* Switch back to editing buffer */
626
XtCallActionProc(otherwindow, "other-window", NULL, NULL, 0);
629
if (source != XawTextGetSource(tags->textwindow) ||
630
right < tags->position || left > tags->position) {
631
XawTextSetInsertionPoint(otherwindow, left);
632
XawTextSetSelection(otherwindow, left, right);