Some more uses of TMParserType instead of int
[geany-mirror.git] / tagmanager / src / tm_workspace.c
blob8e51f84677ba1a3be153b46d0ccfb6cc50373b21
1 /*
3 * Copyright (c) 2001-2002, Biswapesh Chattopadhyay
5 * This source code is released for free distribution under the terms of the
6 * GNU General Public License.
8 */
10 /**
11 * @file tm_workspace.h
12 The TMWorkspace structure is meant to be used as a singleton to store application
13 wide tag information.
15 The workspace is intended to contain a list of global tags
16 and a set of individual source files.
19 #include "general.h"
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <ctype.h>
25 #include <sys/types.h>
26 #include <string.h>
27 #include <sys/stat.h>
28 #ifdef HAVE_GLOB_H
29 # include <glob.h>
30 #endif
31 #include <glib/gstdio.h>
33 #include "tm_workspace.h"
34 #include "tm_tag.h"
35 #include "tm_parser.h"
38 /* when changing, always keep the three sort criteria below in sync */
39 static TMTagAttrType workspace_tags_sort_attrs[] =
41 tm_tag_attr_name_t, tm_tag_attr_file_t, tm_tag_attr_line_t,
42 tm_tag_attr_type_t, tm_tag_attr_scope_t, tm_tag_attr_arglist_t, 0
45 /* for file tags the file is always identical, don't use for sorting */
46 static TMTagAttrType file_tags_sort_attrs[] =
48 tm_tag_attr_name_t, tm_tag_attr_line_t,
49 tm_tag_attr_type_t, tm_tag_attr_scope_t, tm_tag_attr_arglist_t, 0
52 /* global tags don't have file/line information */
53 static TMTagAttrType global_tags_sort_attrs[] =
55 tm_tag_attr_name_t,
56 tm_tag_attr_type_t, tm_tag_attr_scope_t, tm_tag_attr_arglist_t, 0
59 static TMTagType TM_TYPE_WITH_MEMBERS =
60 tm_tag_class_t | tm_tag_struct_t | tm_tag_union_t |
61 tm_tag_enum_t | tm_tag_interface_t;
63 static TMWorkspace *theWorkspace = NULL;
66 static gboolean tm_create_workspace(void)
68 theWorkspace = g_new(TMWorkspace, 1);
69 theWorkspace->tags_array = g_ptr_array_new();
71 theWorkspace->global_tags = g_ptr_array_new();
72 theWorkspace->source_files = g_ptr_array_new();
73 theWorkspace->typename_array = g_ptr_array_new();
74 theWorkspace->global_typename_array = g_ptr_array_new();
75 return TRUE;
79 /* Frees the workspace structure and all child source files. Use only when
80 exiting from the main program.
82 void tm_workspace_free(void)
84 guint i;
86 #ifdef TM_DEBUG
87 g_message("Workspace destroyed");
88 #endif
90 for (i=0; i < theWorkspace->source_files->len; ++i)
91 tm_source_file_free(theWorkspace->source_files->pdata[i]);
92 g_ptr_array_free(theWorkspace->source_files, TRUE);
93 tm_tags_array_free(theWorkspace->global_tags, TRUE);
94 g_ptr_array_free(theWorkspace->tags_array, TRUE);
95 g_ptr_array_free(theWorkspace->typename_array, TRUE);
96 g_ptr_array_free(theWorkspace->global_typename_array, TRUE);
97 g_free(theWorkspace);
98 theWorkspace = NULL;
102 /* Since TMWorkspace is a singleton, you should not create multiple
103 workspaces, but get a pointer to the workspace whenever required. The first
104 time a pointer is requested, or a source file is added to the workspace,
105 a workspace is created. Subsequent calls to the function will return the
106 created workspace.
108 const TMWorkspace *tm_get_workspace(void)
110 if (NULL == theWorkspace)
111 tm_create_workspace();
112 return theWorkspace;
116 static void tm_workspace_merge_tags(GPtrArray **big_array, GPtrArray *small_array)
118 GPtrArray *new_tags = tm_tags_merge(*big_array, small_array, workspace_tags_sort_attrs, FALSE);
119 /* tags owned by TMSourceFile - free just the pointer array */
120 g_ptr_array_free(*big_array, TRUE);
121 *big_array = new_tags;
125 static void merge_extracted_tags(GPtrArray **dest, GPtrArray *src, TMTagType tag_types)
127 GPtrArray *arr;
129 arr = tm_tags_extract(src, tag_types);
130 tm_workspace_merge_tags(dest, arr);
131 g_ptr_array_free(arr, TRUE);
135 static void update_source_file(TMSourceFile *source_file, guchar* text_buf,
136 gsize buf_size, gboolean use_buffer, gboolean update_workspace)
138 #ifdef TM_DEBUG
139 g_message("Source file updating based on source file %s", source_file->file_name);
140 #endif
142 if (update_workspace)
144 /* tm_source_file_parse() deletes the tag objects - remove the tags from
145 * workspace while they exist and can be scanned */
146 tm_tags_remove_file_tags(source_file, theWorkspace->tags_array);
147 tm_tags_remove_file_tags(source_file, theWorkspace->typename_array);
149 tm_source_file_parse(source_file, text_buf, buf_size, use_buffer);
150 tm_tags_sort(source_file->tags_array, file_tags_sort_attrs, FALSE, TRUE);
151 if (update_workspace)
153 #ifdef TM_DEBUG
154 g_message("Updating workspace from source file");
155 #endif
156 tm_workspace_merge_tags(&theWorkspace->tags_array, source_file->tags_array);
158 merge_extracted_tags(&(theWorkspace->typename_array), source_file->tags_array, TM_GLOBAL_TYPE_MASK);
160 #ifdef TM_DEBUG
161 else
162 g_message("Skipping workspace update because update_workspace is %s",
163 update_workspace?"TRUE":"FALSE");
165 #endif
169 /** Adds a source file to the workspace, parses it and updates the workspace tags.
170 @param source_file The source file to add to the workspace.
172 GEANY_API_SYMBOL
173 void tm_workspace_add_source_file(TMSourceFile *source_file)
175 g_return_if_fail(source_file != NULL);
177 g_ptr_array_add(theWorkspace->source_files, source_file);
178 update_source_file(source_file, NULL, 0, FALSE, TRUE);
182 void tm_workspace_add_source_file_noupdate(TMSourceFile *source_file)
184 g_return_if_fail(source_file != NULL);
186 g_ptr_array_add(theWorkspace->source_files, source_file);
190 /* Updates the source file by reparsing the text-buffer passed as parameter.
191 Ctags will use a parsing based on buffer instead of on files.
192 You should call this function when you don't want a previous saving of the file
193 you're editing. It's useful for a "real-time" updating of the tags.
194 The tags array and the tags themselves are destroyed and re-created, hence any
195 other tag arrays pointing to these tags should be rebuilt as well. All sorting
196 information is also lost.
197 @param source_file The source file to update with a buffer.
198 @param text_buf A text buffer. The user should take care of allocate and free it after
199 the use here.
200 @param buf_size The size of text_buf.
202 void tm_workspace_update_source_file_buffer(TMSourceFile *source_file, guchar* text_buf,
203 gsize buf_size)
205 update_source_file(source_file, text_buf, buf_size, TRUE, TRUE);
209 /** Removes a source file from the workspace if it exists. This function also removes
210 the tags belonging to this file from the workspace. To completely free the TMSourceFile
211 pointer call tm_source_file_free() on it.
212 @param source_file Pointer to the source file to be removed.
214 GEANY_API_SYMBOL
215 void tm_workspace_remove_source_file(TMSourceFile *source_file)
217 guint i;
219 g_return_if_fail(source_file != NULL);
221 for (i=0; i < theWorkspace->source_files->len; ++i)
223 if (theWorkspace->source_files->pdata[i] == source_file)
225 tm_tags_remove_file_tags(source_file, theWorkspace->tags_array);
226 tm_tags_remove_file_tags(source_file, theWorkspace->typename_array);
227 g_ptr_array_remove_index_fast(theWorkspace->source_files, i);
228 return;
234 /* Recreates workspace tag array from all member TMSourceFile objects. Use if you
235 want to globally refresh the workspace. This function does not call tm_source_file_update()
236 which should be called before this function on source files which need to be
237 reparsed.
239 static void tm_workspace_update(void)
241 guint i, j;
242 TMSourceFile *source_file;
244 #ifdef TM_DEBUG
245 g_message("Recreating workspace tags array");
246 #endif
248 g_ptr_array_set_size(theWorkspace->tags_array, 0);
250 #ifdef TM_DEBUG
251 g_message("Total %d objects", theWorkspace->source_files->len);
252 #endif
253 for (i=0; i < theWorkspace->source_files->len; ++i)
255 source_file = theWorkspace->source_files->pdata[i];
256 #ifdef TM_DEBUG
257 g_message("Adding tags of %s", source_file->file_name);
258 #endif
259 if (source_file->tags_array->len > 0)
261 for (j = 0; j < source_file->tags_array->len; ++j)
263 g_ptr_array_add(theWorkspace->tags_array,
264 source_file->tags_array->pdata[j]);
268 #ifdef TM_DEBUG
269 g_message("Total: %d tags", theWorkspace->tags_array->len);
270 #endif
271 tm_tags_sort(theWorkspace->tags_array, workspace_tags_sort_attrs, TRUE, FALSE);
273 g_ptr_array_free(theWorkspace->typename_array, TRUE);
274 theWorkspace->typename_array = tm_tags_extract(theWorkspace->tags_array, TM_GLOBAL_TYPE_MASK);
278 /** Adds multiple source files to the workspace and updates the workspace tag arrays.
279 This is more efficient than calling tm_workspace_add_source_file() and
280 tm_workspace_update_source_file() separately for each of the files.
281 @param source_files The source files to be added to the workspace.
283 GEANY_API_SYMBOL
284 void tm_workspace_add_source_files(GPtrArray *source_files)
286 guint i;
288 g_return_if_fail(source_files != NULL);
290 for (i = 0; i < source_files->len; i++)
292 TMSourceFile *source_file = source_files->pdata[i];
294 tm_workspace_add_source_file_noupdate(source_file);
295 update_source_file(source_file, NULL, 0, FALSE, FALSE);
298 tm_workspace_update();
302 /** Removes multiple source files from the workspace and updates the workspace tag
303 arrays. This is more efficient than calling tm_workspace_remove_source_file()
304 separately for each of the files. To completely free the TMSourceFile pointers
305 call tm_source_file_free() on each of them.
306 @param source_files The source files to be removed from the workspace.
308 GEANY_API_SYMBOL
309 void tm_workspace_remove_source_files(GPtrArray *source_files)
311 guint i, j;
313 g_return_if_fail(source_files != NULL);
315 //TODO: sort both arrays by pointer value and remove in single pass
316 for (i = 0; i < source_files->len; i++)
318 TMSourceFile *source_file = source_files->pdata[i];
320 for (j = 0; j < theWorkspace->source_files->len; j++)
322 if (theWorkspace->source_files->pdata[j] == source_file)
324 g_ptr_array_remove_index_fast(theWorkspace->source_files, j);
325 break;
330 tm_workspace_update();
334 /* Loads the global tag list from the specified file. The global tag list should
335 have been first created using tm_workspace_create_global_tags().
336 @param tags_file The file containing global tags.
337 @return TRUE on success, FALSE on failure.
338 @see tm_workspace_create_global_tags()
340 gboolean tm_workspace_load_global_tags(const char *tags_file, TMParserType mode)
342 guchar buf[BUFSIZ];
343 FILE *fp;
344 GPtrArray *file_tags, *new_tags;
345 TMTag *tag;
346 TMFileFormat format = TM_FILE_FORMAT_TAGMANAGER;
348 if (NULL == (fp = g_fopen(tags_file, "r")))
349 return FALSE;
350 if ((NULL == fgets((gchar*) buf, BUFSIZ, fp)) || ('\0' == *buf))
352 fclose(fp);
353 return FALSE; /* early out on error */
355 else
356 { /* We read the first line for the format specification. */
357 if (buf[0] == '#' && strstr((gchar*) buf, "format=pipe") != NULL)
358 format = TM_FILE_FORMAT_PIPE;
359 else if (buf[0] == '#' && strstr((gchar*) buf, "format=tagmanager") != NULL)
360 format = TM_FILE_FORMAT_TAGMANAGER;
361 else if (buf[0] == '#' && strstr((gchar*) buf, "format=ctags") != NULL)
362 format = TM_FILE_FORMAT_CTAGS;
363 else if (strncmp((gchar*) buf, "!_TAG_", 6) == 0)
364 format = TM_FILE_FORMAT_CTAGS;
365 else
366 { /* We didn't find a valid format specification, so we try to auto-detect the format
367 * by counting the pipe characters on the first line and asumme pipe format when
368 * we find more than one pipe on the line. */
369 guint i, pipe_cnt = 0, tab_cnt = 0;
370 for (i = 0; i < BUFSIZ && buf[i] != '\0' && pipe_cnt < 2; i++)
372 if (buf[i] == '|')
373 pipe_cnt++;
374 else if (buf[i] == '\t')
375 tab_cnt++;
377 if (pipe_cnt > 1)
378 format = TM_FILE_FORMAT_PIPE;
379 else if (tab_cnt > 1)
380 format = TM_FILE_FORMAT_CTAGS;
382 rewind(fp); /* reset the file pointer, to start reading again from the beginning */
385 file_tags = g_ptr_array_new();
386 while (NULL != (tag = tm_tag_new_from_file(NULL, fp, mode, format)))
387 g_ptr_array_add(file_tags, tag);
388 fclose(fp);
390 tm_tags_sort(file_tags, global_tags_sort_attrs, TRUE, TRUE);
392 /* reorder the whole array, because tm_tags_find expects a sorted array */
393 new_tags = tm_tags_merge(theWorkspace->global_tags,
394 file_tags, global_tags_sort_attrs, TRUE);
395 g_ptr_array_free(theWorkspace->global_tags, TRUE);
396 g_ptr_array_free(file_tags, TRUE);
397 theWorkspace->global_tags = new_tags;
399 g_ptr_array_free(theWorkspace->global_typename_array, TRUE);
400 theWorkspace->global_typename_array = tm_tags_extract(new_tags, TM_GLOBAL_TYPE_MASK);
402 return TRUE;
406 static guint tm_file_inode_hash(gconstpointer key)
408 GStatBuf file_stat;
409 const char *filename = (const char*)key;
410 if (g_stat(filename, &file_stat) == 0)
412 #ifdef TM_DEBUG
413 g_message ("Hash for '%s' is '%d'\n", filename, file_stat.st_ino);
414 #endif
415 return g_direct_hash ((gpointer)(intptr_t)file_stat.st_ino);
416 } else {
417 return 0;
422 static void tm_move_entries_to_g_list(gpointer key, gpointer value, gpointer user_data)
424 GList **pp_list = (GList**)user_data;
426 if (user_data == NULL)
427 return;
429 *pp_list = g_list_prepend(*pp_list, value);
433 static void write_includes_file(FILE *fp, GList *includes_files)
435 GList *node;
437 node = includes_files;
438 while (node)
440 char *str = g_strdup_printf("#include \"%s\"\n", (char*)node->data);
441 size_t str_len = strlen(str);
443 fwrite(str, str_len, 1, fp);
444 g_free(str);
445 node = g_list_next(node);
450 static void append_to_temp_file(FILE *fp, GList *file_list)
452 GList *node;
454 node = file_list;
455 while (node)
457 const char *fname = node->data;
458 char *contents;
459 size_t length;
460 GError *err = NULL;
462 if (! g_file_get_contents(fname, &contents, &length, &err))
464 fprintf(stderr, "Unable to read file: %s\n", err->message);
465 g_error_free(err);
467 else
469 fwrite(contents, length, 1, fp);
470 fwrite("\n", 1, 1, fp); /* in case file doesn't end in newline (e.g. windows). */
471 g_free(contents);
473 node = g_list_next (node);
478 static gchar *create_temp_file(const gchar *tpl)
480 gchar *name;
481 gint fd;
483 fd = g_file_open_tmp(tpl, &name, NULL);
484 if (fd < 0)
485 name = NULL;
486 else
487 close(fd);
489 return name;
493 /* Creates a list of global tags. Ideally, this should be created once during
494 installations so that all users can use the same file. This is because a full
495 scale global tag list can occupy several megabytes of disk space.
496 @param pre_process The pre-processing command. This is executed via system(),
497 so you can pass stuff like 'gcc -E -dD -P `gnome-config --cflags gnome`'.
498 @param includes Include files to process. Wildcards such as '/usr/include/a*.h'
499 are allowed.
500 @param tags_file The file where the tags will be stored.
501 @param lang The language to use for the tags file.
502 @return TRUE on success, FALSE on failure.
504 gboolean tm_workspace_create_global_tags(const char *pre_process, const char **includes,
505 int includes_count, const char *tags_file, TMParserType lang)
507 #ifdef HAVE_GLOB_H
508 glob_t globbuf;
509 size_t idx_glob;
510 #endif
511 int idx_inc;
512 char *command;
513 guint i;
514 FILE *fp;
515 TMSourceFile *source_file;
516 GPtrArray *tags_array;
517 GHashTable *includes_files_hash;
518 GList *includes_files = NULL;
519 gchar *temp_file = create_temp_file("tmp_XXXXXX.cpp");
520 gchar *temp_file2 = create_temp_file("tmp_XXXXXX.cpp");
522 if (NULL == temp_file || NULL == temp_file2 ||
523 NULL == (fp = g_fopen(temp_file, "w")))
525 g_free(temp_file);
526 g_free(temp_file2);
527 return FALSE;
530 includes_files_hash = g_hash_table_new_full (tm_file_inode_hash,
531 g_direct_equal,
532 NULL, g_free);
534 #ifdef HAVE_GLOB_H
535 globbuf.gl_offs = 0;
537 if (includes[0][0] == '"') /* leading \" char for glob matching */
538 for(idx_inc = 0; idx_inc < includes_count; idx_inc++)
540 size_t dirty_len = strlen(includes[idx_inc]);
541 char *clean_path = g_malloc(dirty_len - 1);
543 strncpy(clean_path, includes[idx_inc] + 1, dirty_len - 1);
544 clean_path[dirty_len - 2] = 0;
546 #ifdef TM_DEBUG
547 g_message ("[o][%s]\n", clean_path);
548 #endif
549 glob(clean_path, 0, NULL, &globbuf);
551 #ifdef TM_DEBUG
552 g_message ("matches: %d\n", globbuf.gl_pathc);
553 #endif
555 for(idx_glob = 0; idx_glob < globbuf.gl_pathc; idx_glob++)
557 #ifdef TM_DEBUG
558 g_message (">>> %s\n", globbuf.gl_pathv[idx_glob]);
559 #endif
560 if (!g_hash_table_lookup(includes_files_hash,
561 globbuf.gl_pathv[idx_glob]))
563 char* file_name_copy = strdup(globbuf.gl_pathv[idx_glob]);
564 g_hash_table_insert(includes_files_hash, file_name_copy,
565 file_name_copy);
566 #ifdef TM_DEBUG
567 g_message ("Added ...\n");
568 #endif
571 globfree(&globbuf);
572 g_free(clean_path);
574 else
575 #endif
576 /* no glob support or globbing not wanted */
577 for(idx_inc = 0; idx_inc < includes_count; idx_inc++)
579 if (!g_hash_table_lookup(includes_files_hash,
580 includes[idx_inc]))
582 char* file_name_copy = strdup(includes[idx_inc]);
583 g_hash_table_insert(includes_files_hash, file_name_copy,
584 file_name_copy);
588 /* Checks for duplicate file entries which would case trouble */
589 g_hash_table_foreach(includes_files_hash, tm_move_entries_to_g_list,
590 &includes_files);
592 includes_files = g_list_reverse (includes_files);
594 #ifdef TM_DEBUG
595 g_message ("writing out files to %s\n", temp_file);
596 #endif
597 if (pre_process != NULL)
598 write_includes_file(fp, includes_files);
599 else
600 append_to_temp_file(fp, includes_files);
602 g_list_free (includes_files);
603 g_hash_table_destroy(includes_files_hash);
604 includes_files_hash = NULL;
605 includes_files = NULL;
606 fclose(fp);
608 if (pre_process != NULL)
610 gint ret;
611 gchar *tmp_errfile = create_temp_file("tmp_XXXXXX");
612 gchar *errors = NULL;
613 command = g_strdup_printf("%s %s >%s 2>%s",
614 pre_process, temp_file, temp_file2, tmp_errfile);
615 #ifdef TM_DEBUG
616 g_message("Executing: %s", command);
617 #endif
618 ret = system(command);
619 g_free(command);
620 g_unlink(temp_file);
621 g_free(temp_file);
622 g_file_get_contents(tmp_errfile, &errors, NULL, NULL);
623 if (errors && *errors)
624 g_printerr("%s", errors);
625 g_free(errors);
626 g_unlink(tmp_errfile);
627 g_free(tmp_errfile);
628 if (ret == -1)
630 g_unlink(temp_file2);
631 return FALSE;
634 else
636 /* no pre-processing needed, so temp_file2 = temp_file */
637 g_unlink(temp_file2);
638 g_free(temp_file2);
639 temp_file2 = temp_file;
640 temp_file = NULL;
642 source_file = tm_source_file_new(temp_file2, tm_source_file_get_lang_name(lang));
643 update_source_file(source_file, NULL, 0, FALSE, FALSE);
644 if (NULL == source_file)
646 g_unlink(temp_file2);
647 return FALSE;
649 g_unlink(temp_file2);
650 g_free(temp_file2);
651 if (0 == source_file->tags_array->len)
653 tm_source_file_free(source_file);
654 return FALSE;
656 tags_array = tm_tags_extract(source_file->tags_array, tm_tag_max_t);
657 if ((NULL == tags_array) || (0 == tags_array->len))
659 if (tags_array)
660 g_ptr_array_free(tags_array, TRUE);
661 tm_source_file_free(source_file);
662 return FALSE;
664 if (FALSE == tm_tags_sort(tags_array, global_tags_sort_attrs, TRUE, FALSE))
666 tm_source_file_free(source_file);
667 return FALSE;
669 if (NULL == (fp = g_fopen(tags_file, "w")))
671 tm_source_file_free(source_file);
672 return FALSE;
674 fprintf(fp, "# format=tagmanager\n");
675 for (i = 0; i < tags_array->len; ++i)
677 tm_tag_write(TM_TAG(tags_array->pdata[i]), fp, tm_tag_attr_type_t
678 | tm_tag_attr_scope_t | tm_tag_attr_arglist_t | tm_tag_attr_vartype_t
679 | tm_tag_attr_pointer_t);
681 fclose(fp);
682 tm_source_file_free(source_file);
683 g_ptr_array_free(tags_array, TRUE);
684 return TRUE;
688 static void fill_find_tags_array(GPtrArray *dst, const GPtrArray *src,
689 const char *name, const char *scope, TMTagType type, TMParserType lang)
691 TMTag **tag;
692 guint i, num;
694 if (!src || !dst || !name || !*name)
695 return;
697 tag = tm_tags_find(src, name, FALSE, &num);
698 for (i = 0; i < num; ++i)
700 if ((type & (*tag)->type) &&
701 tm_tag_langs_compatible(lang, (*tag)->lang) &&
702 (!scope || g_strcmp0((*tag)->scope, scope) == 0))
704 g_ptr_array_add(dst, *tag);
706 tag++;
711 /* Returns all matching tags found in the workspace.
712 @param name The name of the tag to find.
713 @param scope The scope name of the tag to find, or NULL.
714 @param type The tag types to return (TMTagType). Can be a bitmask.
715 @param attrs The attributes to sort and dedup on (0 terminated integer array).
716 @param lang Specifies the language(see the table in parsers.h) of the tags to be found,
717 -1 for all
718 @return Array of matching tags.
720 GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type,
721 TMTagAttrType *attrs, TMParserType lang)
723 GPtrArray *tags = g_ptr_array_new();
725 fill_find_tags_array(tags, theWorkspace->tags_array, name, scope, type, lang);
726 fill_find_tags_array(tags, theWorkspace->global_tags, name, scope, type, lang);
728 if (attrs)
729 tm_tags_sort(tags, attrs, TRUE, FALSE);
731 return tags;
735 static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src,
736 const char *name, TMParserType lang, guint max_num)
738 TMTag **tag, *last = NULL;
739 guint i, count, num;
741 if (!src || !dst || !name || !*name)
742 return;
744 num = 0;
745 tag = tm_tags_find(src, name, TRUE, &count);
746 for (i = 0; i < count && num < max_num; ++i)
748 if (tm_tag_langs_compatible(lang, (*tag)->lang) &&
749 !tm_tag_is_anon(*tag) &&
750 (!last || g_strcmp0(last->name, (*tag)->name) != 0))
752 g_ptr_array_add(dst, *tag);
753 last = *tag;
754 num++;
756 tag++;
761 /* Returns tags with the specified prefix sorted by name. If there are several
762 tags with the same name, only one of them appears in the resulting array.
763 @param prefix The prefix of the tag to find.
764 @param lang Specifies the language(see the table in parsers.h) of the tags to be found,
765 -1 for all.
766 @param max_num The maximum number of tags to return.
767 @return Array of matching tags sorted by their name.
769 GPtrArray *tm_workspace_find_prefix(const char *prefix, TMParserType lang, guint max_num)
771 TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 };
772 GPtrArray *tags = g_ptr_array_new();
774 fill_find_tags_array_prefix(tags, theWorkspace->tags_array, prefix, lang, max_num);
775 fill_find_tags_array_prefix(tags, theWorkspace->global_tags, prefix, lang, max_num);
777 tm_tags_sort(tags, attrs, TRUE, FALSE);
778 if (tags->len > max_num)
779 tags->len = max_num;
781 return tags;
785 /* Gets all members of type_tag; search them inside the all array.
786 * The namespace parameter determines whether we are performing the "namespace"
787 * search (user has typed something like "A::" where A is a type) or "scope" search
788 * (user has typed "a." where a is a global struct-like variable). With the
789 * namespace search we return all direct descendants of any type while with the
790 * scope search we return only those which can be invoked on a variable (member,
791 * method, etc.). */
792 static GPtrArray *
793 find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespace)
795 TMTagType member_types = tm_tag_max_t & ~(TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t);
796 GPtrArray *tags = g_ptr_array_new();
797 gchar *scope;
798 guint i;
800 if (namespace)
801 member_types = tm_tag_max_t;
803 if (type_tag->scope && *(type_tag->scope))
804 scope = g_strconcat(type_tag->scope, tm_tag_context_separator(type_tag->lang), type_tag->name, NULL);
805 else
806 scope = g_strdup(type_tag->name);
808 for (i = 0; i < all->len; ++i)
810 TMTag *tag = TM_TAG (all->pdata[i]);
812 if (tag && (tag->type & member_types) &&
813 tag->scope && tag->scope[0] != '\0' &&
814 tm_tag_langs_compatible(tag->lang, type_tag->lang) &&
815 strcmp(scope, tag->scope) == 0 &&
816 (!namespace || !tm_tag_is_anon(tag)))
818 g_ptr_array_add (tags, tag);
822 g_free(scope);
824 if (tags->len == 0)
826 g_ptr_array_free(tags, TRUE);
827 return NULL;
830 return tags;
834 static gchar *strip_type(const gchar *scoped_name, TMParserType lang)
836 if (scoped_name != NULL)
838 /* remove scope prefix */
839 const gchar *sep = tm_tag_context_separator(lang);
840 const gchar *base = g_strrstr(scoped_name, sep);
841 gchar *name = base ? g_strdup(base + strlen(sep)) : g_strdup(scoped_name);
843 /* remove pointers */
844 g_strdelimit(name, "*^", ' ');
845 g_strstrip(name);
847 return name;
849 return NULL;
853 /* Gets all members of the type with the given name; search them inside tags_array */
854 static GPtrArray *
855 find_scope_members (const GPtrArray *tags_array, const gchar *name, TMSourceFile *file,
856 TMParserType lang, gboolean namespace)
858 GPtrArray *res = NULL;
859 gchar *type_name;
860 guint i;
862 g_return_val_if_fail(name && *name, NULL);
864 type_name = g_strdup(name);
866 /* Check if type_name is a type that can possibly contain members.
867 * Try to resolve intermediate typedefs to get the real type name. Also
868 * add scope information to the name if applicable.
869 * The loop below loops only when resolving typedefs - avoid possibly infinite
870 * loop when typedefs create a cycle by adding some limits. */
871 for (i = 0; i < 5; i++)
873 guint j;
874 GPtrArray *type_tags;
875 TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t;
876 TMTag *tag = NULL;
878 if (!namespace)
879 types &= ~tm_tag_enum_t;
881 type_tags = g_ptr_array_new();
882 fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, lang);
884 for (j = 0; j < type_tags->len; j++)
886 TMTag *test_tag = TM_TAG(type_tags->pdata[j]);
888 /* anonymous type defined in a different file than the variable -
889 * this isn't the type we are looking for */
890 if (tm_tag_is_anon(test_tag) && (file != test_tag->file || test_tag->file == NULL))
891 continue;
893 tag = test_tag;
895 /* prefer non-typedef tags because we can be sure they contain members */
896 if (test_tag->type != tm_tag_typedef_t)
897 break;
900 g_ptr_array_free(type_tags, TRUE);
902 if (!tag) /* not a type that can contain members */
903 break;
905 /* intermediate typedef - resolve to the real type */
906 if (tag->type == tm_tag_typedef_t)
908 if (tag->var_type && tag->var_type[0] != '\0')
910 g_free(type_name);
911 type_name = strip_type(tag->var_type, tag->lang);
912 file = tag->file;
913 continue;
915 break;
917 else /* real type with members */
919 /* use the same file as the composite type if file information available */
920 res = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace);
921 break;
925 g_free(type_name);
927 return res;
931 /* Checks whether a member tag is directly accessible from method with method_scope */
932 static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *method_scope, TMTag *member_tag,
933 TMParserType lang)
935 const gchar *sep = tm_tag_context_separator(lang);
936 gboolean ret = FALSE;
937 gchar **comps;
938 guint len;
940 /* method scope is in the form ...::class_name::method_name */
941 comps = g_strsplit (method_scope, sep, 0);
942 len = g_strv_length(comps);
943 if (len > 1)
945 gchar *method, *member_scope, *cls, *cls_scope;
947 /* get method/member scope */
948 method = comps[len - 1];
949 comps[len - 1] = NULL;
950 member_scope = g_strjoinv(sep, comps);
951 comps[len - 1] = method;
953 /* get class scope */
954 cls = comps[len - 2];
955 comps[len - 2] = NULL;
956 cls_scope = g_strjoinv(sep, comps);
957 comps[len - 2] = cls;
958 cls_scope = strlen(cls_scope) > 0 ? cls_scope : NULL;
960 /* check whether member inside the class */
961 if (g_strcmp0(member_tag->scope, member_scope) == 0)
963 const GPtrArray *src = member_tag->file ? member_tag->file->tags_array : tags;
964 GPtrArray *cls_tags = g_ptr_array_new();
966 /* check whether the class exists */
967 fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS | tm_tag_namespace_t, lang);
968 ret = cls_tags->len > 0;
969 g_ptr_array_free(cls_tags, TRUE);
972 g_free(cls_scope);
973 g_free(member_scope);
976 g_strfreev(comps);
977 return ret;
981 /* For an array of variable/type tags, find members inside the types */
982 static GPtrArray *
983 find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, TMParserType lang,
984 gboolean member, const gchar *current_scope)
986 GPtrArray *member_tags = NULL;
987 guint i;
989 /* there may be several variables/types with the same name - try each of them until
990 * we find something */
991 for (i = 0; i < tags->len && !member_tags; i++)
993 TMTag *tag = TM_TAG(tags->pdata[i]);
994 TMTagType member_types = tm_tag_member_t | tm_tag_field_t | tm_tag_method_t;
995 TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t;
997 if (tag->type & types) /* type: namespace search */
999 if (tag->type & tm_tag_typedef_t)
1000 member_tags = find_scope_members(searched_array, tag->name, tag->file, lang, TRUE);
1001 else
1002 member_tags = find_scope_members_tags(tag->file ? tag->file->tags_array : searched_array,
1003 tag, TRUE);
1005 else if (tag->var_type) /* variable: scope search */
1007 /* The question now is whether we should use member tags (such as
1008 * tm_tag_field_t, tm_tag_member_t) or not. We want them if member==TRUE
1009 * (which means user has typed something like foo.bar.) or if we are
1010 * inside a method where foo is a class member, we want scope completion
1011 * for foo. */
1012 if (!(tag->type & member_types) || member ||
1013 member_at_method_scope(tags, current_scope, tag, lang))
1015 gchar *tag_type = strip_type(tag->var_type, tag->lang);
1017 member_tags = find_scope_members(searched_array, tag_type, tag->file, lang, FALSE);
1018 g_free(tag_type);
1023 return member_tags;
1027 static GPtrArray *find_namespace_members_all(const GPtrArray *tags, const GPtrArray *searched_array, TMParserType lang)
1029 GPtrArray *member_tags = NULL;
1030 guint i;
1032 for (i = 0; i < tags->len && !member_tags; i++)
1034 TMTag *tag = TM_TAG(tags->pdata[i]);
1036 member_tags = find_scope_members_tags(searched_array, tag, TRUE);
1039 return member_tags;
1043 /* Returns all member tags of a struct/union/class if the provided name is a variable
1044 of such a type or the name of the type.
1045 @param source_file TMSourceFile of the edited source file
1046 @param name Name of the variable/type whose members are searched
1047 @param function TRUE if the name is a name of a function
1048 @param member TRUE if invoked on class/struct member (e.g. after the last dot in foo.bar.)
1049 @param current_scope The current scope in the editor
1050 @param search_namespace Whether to search the contents of namespace (e.g. after MyNamespace::)
1051 @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */
1052 GPtrArray *
1053 tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name,
1054 gboolean function, gboolean member, const gchar *current_scope, gboolean search_namespace)
1056 TMParserType lang = source_file->lang;
1057 GPtrArray *tags, *member_tags = NULL;
1058 TMTagType function_types = tm_tag_function_t | tm_tag_method_t |
1059 tm_tag_macro_with_arg_t | tm_tag_prototype_t;
1060 TMTagType tag_type = tm_tag_max_t &
1061 ~(function_types | tm_tag_enumerator_t | tm_tag_namespace_t | tm_tag_package_t);
1062 TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, 0};
1064 if (search_namespace)
1066 tags = tm_workspace_find(name, NULL, tm_tag_namespace_t, NULL, lang);
1068 member_tags = find_namespace_members_all(tags, theWorkspace->tags_array, lang);
1069 if (!member_tags)
1070 member_tags = find_namespace_members_all(tags, theWorkspace->global_tags, lang);
1072 g_ptr_array_free(tags, TRUE);
1075 if (!member_tags)
1077 if (function)
1078 tag_type = function_types;
1080 /* tags corresponding to the variable/type name */
1081 tags = tm_workspace_find(name, NULL, tag_type, NULL, lang);
1083 /* Start searching inside the source file, continue with workspace tags and
1084 * end with global tags. This way we find the "closest" tag to the current
1085 * file in case there are more of them. */
1086 if (source_file)
1087 member_tags = find_scope_members_all(tags, source_file->tags_array,
1088 lang, member, current_scope);
1089 if (!member_tags)
1090 member_tags = find_scope_members_all(tags, theWorkspace->tags_array, lang,
1091 member, current_scope);
1092 if (!member_tags)
1093 member_tags = find_scope_members_all(tags, theWorkspace->global_tags, lang,
1094 member, current_scope);
1096 g_ptr_array_free(tags, TRUE);
1099 tm_tags_dedup(member_tags, sort_attr, FALSE);
1101 return member_tags;
1105 #ifdef TM_DEBUG
1107 /* Dumps the workspace tree - useful for debugging */
1108 void tm_workspace_dump(void)
1110 guint i;
1112 #ifdef TM_DEBUG
1113 g_message("Dumping TagManager workspace tree..");
1114 #endif
1115 for (i=0; i < theWorkspace->source_files->len; ++i)
1117 TMSourceFile *source_file = theWorkspace->source_files->pdata[i];
1118 fprintf(stderr, "%s", source_file->file_name);
1121 #endif /* TM_DEBUG */
1124 #if 0
1126 /* Returns a list of parent classes for the given class name
1127 @param name Name of the class
1128 @return A GPtrArray of TMTag pointers (includes the TMTag for the class) */
1129 static const GPtrArray *tm_workspace_get_parents(const gchar *name)
1131 static TMTagAttrType type[] = { tm_tag_attr_name_t, tm_tag_attr_none_t };
1132 static GPtrArray *parents = NULL;
1133 const GPtrArray *matches;
1134 guint i = 0;
1135 guint j;
1136 gchar **klasses;
1137 gchar **klass;
1138 TMTag *tag;
1140 g_return_val_if_fail(name && isalpha(*name),NULL);
1142 if (NULL == parents)
1143 parents = g_ptr_array_new();
1144 else
1145 g_ptr_array_set_size(parents, 0);
1146 matches = tm_workspace_find(name, NULL, tm_tag_class_t, type, -1);
1147 if ((NULL == matches) || (0 == matches->len))
1148 return NULL;
1149 g_ptr_array_add(parents, matches->pdata[0]);
1150 while (i < parents->len)
1152 tag = TM_TAG(parents->pdata[i]);
1153 if ((NULL != tag->inheritance) && (isalpha(tag->inheritance[0])))
1155 klasses = g_strsplit(tag->inheritance, ",", 10);
1156 for (klass = klasses; (NULL != *klass); ++ klass)
1158 for (j=0; j < parents->len; ++j)
1160 if (0 == strcmp(*klass, TM_TAG(parents->pdata[j])->name))
1161 break;
1163 if (parents->len == j)
1165 matches = tm_workspace_find(*klass, NULL, tm_tag_class_t, type, -1);
1166 if ((NULL != matches) && (0 < matches->len))
1167 g_ptr_array_add(parents, matches->pdata[0]);
1170 g_strfreev(klasses);
1172 ++ i;
1174 return parents;
1177 #endif