TM: Fix actually running the mapping self-tests
[geany-mirror.git] / tagmanager / src / tm_workspace.c
blobbdf11ee293572725e4878e5689f47a6c84a72d3d
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_ctags_wrappers.h"
35 #include "tm_tag.h"
36 #include "tm_parser.h"
39 /* when changing, always keep the three sort criteria below in sync */
40 static TMTagAttrType workspace_tags_sort_attrs[] =
42 tm_tag_attr_name_t, tm_tag_attr_file_t, tm_tag_attr_line_t,
43 tm_tag_attr_type_t, tm_tag_attr_scope_t, tm_tag_attr_arglist_t, 0
46 /* for file tags the file is always identical, don't use for sorting */
47 static TMTagAttrType file_tags_sort_attrs[] =
49 tm_tag_attr_name_t, tm_tag_attr_line_t,
50 tm_tag_attr_type_t, tm_tag_attr_scope_t, tm_tag_attr_arglist_t, 0
53 /* global tags don't have file/line information */
54 static TMTagAttrType global_tags_sort_attrs[] =
56 tm_tag_attr_name_t,
57 tm_tag_attr_type_t, tm_tag_attr_scope_t, tm_tag_attr_arglist_t, 0
60 static TMTagType TM_TYPE_WITH_MEMBERS =
61 tm_tag_class_t | tm_tag_struct_t | tm_tag_union_t |
62 tm_tag_enum_t | tm_tag_interface_t;
64 static TMTagType TM_GLOBAL_TYPE_MASK =
65 tm_tag_class_t | tm_tag_enum_t | tm_tag_interface_t |
66 tm_tag_struct_t | tm_tag_typedef_t | tm_tag_union_t | tm_tag_namespace_t;
68 static TMWorkspace *theWorkspace = NULL;
71 static gboolean tm_create_workspace(void)
73 theWorkspace = g_new(TMWorkspace, 1);
74 theWorkspace->tags_array = g_ptr_array_new();
76 theWorkspace->global_tags = g_ptr_array_new();
77 theWorkspace->source_files = g_ptr_array_new();
78 theWorkspace->typename_array = g_ptr_array_new();
79 theWorkspace->global_typename_array = g_ptr_array_new();
81 tm_ctags_init();
82 tm_parser_verify_type_mappings();
84 return TRUE;
88 /* Frees the workspace structure and all child source files. Use only when
89 exiting from the main program.
91 void tm_workspace_free(void)
93 guint i;
95 #ifdef TM_DEBUG
96 g_message("Workspace destroyed");
97 #endif
99 for (i=0; i < theWorkspace->source_files->len; ++i)
100 tm_source_file_free(theWorkspace->source_files->pdata[i]);
101 g_ptr_array_free(theWorkspace->source_files, TRUE);
102 tm_tags_array_free(theWorkspace->global_tags, TRUE);
103 g_ptr_array_free(theWorkspace->tags_array, TRUE);
104 g_ptr_array_free(theWorkspace->typename_array, TRUE);
105 g_ptr_array_free(theWorkspace->global_typename_array, TRUE);
106 g_free(theWorkspace);
107 theWorkspace = NULL;
111 /* Since TMWorkspace is a singleton, you should not create multiple
112 workspaces, but get a pointer to the workspace whenever required. The first
113 time a pointer is requested, or a source file is added to the workspace,
114 a workspace is created. Subsequent calls to the function will return the
115 created workspace.
117 const TMWorkspace *tm_get_workspace(void)
119 if (NULL == theWorkspace)
120 tm_create_workspace();
121 return theWorkspace;
125 static void tm_workspace_merge_tags(GPtrArray **big_array, GPtrArray *small_array)
127 GPtrArray *new_tags = tm_tags_merge(*big_array, small_array, workspace_tags_sort_attrs, FALSE);
128 /* tags owned by TMSourceFile - free just the pointer array */
129 g_ptr_array_free(*big_array, TRUE);
130 *big_array = new_tags;
134 static void merge_extracted_tags(GPtrArray **dest, GPtrArray *src, TMTagType tag_types)
136 GPtrArray *arr;
138 arr = tm_tags_extract(src, tag_types);
139 tm_workspace_merge_tags(dest, arr);
140 g_ptr_array_free(arr, TRUE);
144 static void update_source_file(TMSourceFile *source_file, guchar* text_buf,
145 gsize buf_size, gboolean use_buffer, gboolean update_workspace)
147 #ifdef TM_DEBUG
148 g_message("Source file updating based on source file %s", source_file->file_name);
149 #endif
151 if (update_workspace)
153 /* tm_source_file_parse() deletes the tag objects - remove the tags from
154 * workspace while they exist and can be scanned */
155 tm_tags_remove_file_tags(source_file, theWorkspace->tags_array);
156 tm_tags_remove_file_tags(source_file, theWorkspace->typename_array);
158 tm_source_file_parse(source_file, text_buf, buf_size, use_buffer);
159 tm_tags_sort(source_file->tags_array, file_tags_sort_attrs, FALSE, TRUE);
160 if (update_workspace)
162 #ifdef TM_DEBUG
163 g_message("Updating workspace from source file");
164 #endif
165 tm_workspace_merge_tags(&theWorkspace->tags_array, source_file->tags_array);
167 merge_extracted_tags(&(theWorkspace->typename_array), source_file->tags_array, TM_GLOBAL_TYPE_MASK);
169 #ifdef TM_DEBUG
170 else
171 g_message("Skipping workspace update because update_workspace is %s",
172 update_workspace?"TRUE":"FALSE");
174 #endif
178 /** Adds a source file to the workspace, parses it and updates the workspace tags.
179 @param source_file The source file to add to the workspace.
181 GEANY_API_SYMBOL
182 void tm_workspace_add_source_file(TMSourceFile *source_file)
184 g_return_if_fail(source_file != NULL);
186 g_ptr_array_add(theWorkspace->source_files, source_file);
187 update_source_file(source_file, NULL, 0, FALSE, TRUE);
191 void tm_workspace_add_source_file_noupdate(TMSourceFile *source_file)
193 g_return_if_fail(source_file != NULL);
195 g_ptr_array_add(theWorkspace->source_files, source_file);
199 /* Updates the source file by reparsing the text-buffer passed as parameter.
200 Ctags will use a parsing based on buffer instead of on files.
201 You should call this function when you don't want a previous saving of the file
202 you're editing. It's useful for a "real-time" updating of the tags.
203 The tags array and the tags themselves are destroyed and re-created, hence any
204 other tag arrays pointing to these tags should be rebuilt as well. All sorting
205 information is also lost.
206 @param source_file The source file to update with a buffer.
207 @param text_buf A text buffer. The user should take care of allocate and free it after
208 the use here.
209 @param buf_size The size of text_buf.
211 void tm_workspace_update_source_file_buffer(TMSourceFile *source_file, guchar* text_buf,
212 gsize buf_size)
214 update_source_file(source_file, text_buf, buf_size, TRUE, TRUE);
218 /** Removes a source file from the workspace if it exists. This function also removes
219 the tags belonging to this file from the workspace. To completely free the TMSourceFile
220 pointer call tm_source_file_free() on it.
221 @param source_file Pointer to the source file to be removed.
223 GEANY_API_SYMBOL
224 void tm_workspace_remove_source_file(TMSourceFile *source_file)
226 guint i;
228 g_return_if_fail(source_file != NULL);
230 for (i=0; i < theWorkspace->source_files->len; ++i)
232 if (theWorkspace->source_files->pdata[i] == source_file)
234 tm_tags_remove_file_tags(source_file, theWorkspace->tags_array);
235 tm_tags_remove_file_tags(source_file, theWorkspace->typename_array);
236 g_ptr_array_remove_index_fast(theWorkspace->source_files, i);
237 return;
243 /* Recreates workspace tag array from all member TMSourceFile objects. Use if you
244 want to globally refresh the workspace. This function does not call tm_source_file_update()
245 which should be called before this function on source files which need to be
246 reparsed.
248 static void tm_workspace_update(void)
250 guint i, j;
251 TMSourceFile *source_file;
253 #ifdef TM_DEBUG
254 g_message("Recreating workspace tags array");
255 #endif
257 g_ptr_array_set_size(theWorkspace->tags_array, 0);
259 #ifdef TM_DEBUG
260 g_message("Total %d objects", theWorkspace->source_files->len);
261 #endif
262 for (i=0; i < theWorkspace->source_files->len; ++i)
264 source_file = theWorkspace->source_files->pdata[i];
265 #ifdef TM_DEBUG
266 g_message("Adding tags of %s", source_file->file_name);
267 #endif
268 if (source_file->tags_array->len > 0)
270 for (j = 0; j < source_file->tags_array->len; ++j)
272 g_ptr_array_add(theWorkspace->tags_array,
273 source_file->tags_array->pdata[j]);
277 #ifdef TM_DEBUG
278 g_message("Total: %d tags", theWorkspace->tags_array->len);
279 #endif
280 tm_tags_sort(theWorkspace->tags_array, workspace_tags_sort_attrs, TRUE, FALSE);
282 g_ptr_array_free(theWorkspace->typename_array, TRUE);
283 theWorkspace->typename_array = tm_tags_extract(theWorkspace->tags_array, TM_GLOBAL_TYPE_MASK);
287 /** Adds multiple source files to the workspace and updates the workspace tag arrays.
288 This is more efficient than calling tm_workspace_add_source_file() and
289 tm_workspace_update_source_file() separately for each of the files.
290 @param source_files @elementtype{TMSourceFile} The source files to be added to the workspace.
292 GEANY_API_SYMBOL
293 void tm_workspace_add_source_files(GPtrArray *source_files)
295 guint i;
297 g_return_if_fail(source_files != NULL);
299 for (i = 0; i < source_files->len; i++)
301 TMSourceFile *source_file = source_files->pdata[i];
303 tm_workspace_add_source_file_noupdate(source_file);
304 update_source_file(source_file, NULL, 0, FALSE, FALSE);
307 tm_workspace_update();
311 /** Removes multiple source files from the workspace and updates the workspace tag
312 arrays. This is more efficient than calling tm_workspace_remove_source_file()
313 separately for each of the files. To completely free the TMSourceFile pointers
314 call tm_source_file_free() on each of them.
315 @param source_files @elementtype{TMSourceFile} The source files to be removed from the workspace.
317 GEANY_API_SYMBOL
318 void tm_workspace_remove_source_files(GPtrArray *source_files)
320 guint i, j;
322 g_return_if_fail(source_files != NULL);
324 //TODO: sort both arrays by pointer value and remove in single pass
325 for (i = 0; i < source_files->len; i++)
327 TMSourceFile *source_file = source_files->pdata[i];
329 for (j = 0; j < theWorkspace->source_files->len; j++)
331 if (theWorkspace->source_files->pdata[j] == source_file)
333 g_ptr_array_remove_index_fast(theWorkspace->source_files, j);
334 break;
339 tm_workspace_update();
343 /* Loads the global tag list from the specified file. The global tag list should
344 have been first created using tm_workspace_create_global_tags().
345 @param tags_file The file containing global tags.
346 @return TRUE on success, FALSE on failure.
347 @see tm_workspace_create_global_tags()
349 gboolean tm_workspace_load_global_tags(const char *tags_file, TMParserType mode)
351 GPtrArray *file_tags, *new_tags;
353 file_tags = tm_source_file_read_tags_file(tags_file, mode);
354 if (!file_tags)
355 return FALSE;
357 tm_tags_sort(file_tags, global_tags_sort_attrs, TRUE, TRUE);
359 /* reorder the whole array, because tm_tags_find expects a sorted array */
360 new_tags = tm_tags_merge(theWorkspace->global_tags,
361 file_tags, global_tags_sort_attrs, TRUE);
362 g_ptr_array_free(theWorkspace->global_tags, TRUE);
363 g_ptr_array_free(file_tags, TRUE);
364 theWorkspace->global_tags = new_tags;
366 g_ptr_array_free(theWorkspace->global_typename_array, TRUE);
367 theWorkspace->global_typename_array = tm_tags_extract(new_tags, TM_GLOBAL_TYPE_MASK);
369 return TRUE;
373 static guint tm_file_inode_hash(gconstpointer key)
375 GStatBuf file_stat;
376 const char *filename = (const char*)key;
378 if (g_stat(filename, &file_stat) == 0)
380 #ifdef TM_DEBUG
381 g_message ("Hash for '%s' is '%d'\n", filename, file_stat.st_ino);
382 #endif
383 return g_direct_hash ((gpointer)(intptr_t)file_stat.st_ino);
386 return 0;
390 static void tm_move_entries_to_g_list(gpointer key, gpointer value, gpointer user_data)
392 GList **pp_list = (GList**)user_data;
394 if (user_data == NULL)
395 return;
397 *pp_list = g_list_prepend(*pp_list, g_strdup(value));
401 static gboolean write_includes_file(const gchar *outf, GList *includes_files)
403 FILE *fp = g_fopen(outf, "w");
404 GList *node = includes_files;
406 if (!fp)
407 return FALSE;
409 while (node)
411 char *str = g_strdup_printf("#include \"%s\"\n", (char*)node->data);
412 size_t str_len = strlen(str);
414 fwrite(str, str_len, 1, fp);
415 g_free(str);
416 node = g_list_next(node);
419 return fclose(fp) == 0;
423 static gboolean combine_include_files(const gchar *outf, GList *file_list)
425 FILE *fp = g_fopen(outf, "w");
426 GList *node = file_list;
428 if (!fp)
429 return FALSE;
431 while (node)
433 const char *fname = node->data;
434 char *contents;
435 size_t length;
436 GError *err = NULL;
438 if (! g_file_get_contents(fname, &contents, &length, &err))
440 fprintf(stderr, "Unable to read file: %s\n", err->message);
441 g_error_free(err);
443 else
445 fwrite(contents, length, 1, fp);
446 fwrite("\n", 1, 1, fp); /* in case file doesn't end in newline (e.g. windows). */
447 g_free(contents);
449 node = g_list_next (node);
452 return fclose(fp) == 0;
456 static gchar *create_temp_file(const gchar *tpl)
458 gchar *name;
459 gint fd;
461 fd = g_file_open_tmp(tpl, &name, NULL);
462 if (fd < 0)
463 name = NULL;
464 else
465 close(fd);
467 return name;
470 static GList *lookup_includes(const gchar **includes, gint includes_count)
472 GList *includes_files = NULL;
473 GHashTable *table;
474 gint i;
475 #ifdef HAVE_GLOB_H
476 glob_t globbuf;
477 size_t idx_glob;
478 #endif
480 table = g_hash_table_new_full(tm_file_inode_hash, g_direct_equal, NULL, g_free);
482 #ifdef HAVE_GLOB_H
483 globbuf.gl_offs = 0;
485 if (includes[0][0] == '"') /* leading \" char for glob matching */
487 for (i = 0; i < includes_count; i++)
489 size_t dirty_len = strlen(includes[i]);
490 gchar *clean_path;
492 if (dirty_len < 2)
493 continue;
495 clean_path = g_malloc(dirty_len - 1);
497 strncpy(clean_path, includes[i] + 1, dirty_len - 1);
498 clean_path[dirty_len - 2] = 0;
500 #ifdef TM_DEBUG
501 g_message ("[o][%s]\n", clean_path);
502 #endif
503 glob(clean_path, 0, NULL, &globbuf);
505 #ifdef TM_DEBUG
506 g_message ("matches: %d\n", globbuf.gl_pathc);
507 #endif
509 for (idx_glob = 0; idx_glob < globbuf.gl_pathc; idx_glob++)
511 #ifdef TM_DEBUG
512 g_message (">>> %s\n", globbuf.gl_pathv[idx_glob]);
513 #endif
514 if (!g_hash_table_lookup(table, globbuf.gl_pathv[idx_glob]))
516 gchar *file_name_copy = g_strdup(globbuf.gl_pathv[idx_glob]);
518 g_hash_table_insert(table, file_name_copy, file_name_copy);
519 #ifdef TM_DEBUG
520 g_message ("Added ...\n");
521 #endif
524 globfree(&globbuf);
525 g_free(clean_path);
528 else
529 #endif /* HAVE_GLOB_H */
531 /* no glob support or globbing not wanted */
532 for (i = 0; i < includes_count; i++)
534 if (!g_hash_table_lookup(table, includes[i]))
536 gchar* file_name_copy = g_strdup(includes[i]);
538 g_hash_table_insert(table, file_name_copy, file_name_copy);
543 g_hash_table_foreach(table, tm_move_entries_to_g_list, &includes_files);
544 g_hash_table_destroy(table);
546 return includes_files;
549 static gchar *pre_process_file(const gchar *cmd, const gchar *inf)
551 gint ret;
552 gchar *outf = create_temp_file("tmp_XXXXXX.cpp");
553 gchar *tmp_errfile;
554 gchar *errors = NULL;
555 gchar *command;
557 if (!outf)
558 return NULL;
560 tmp_errfile = create_temp_file("tmp_XXXXXX");
561 if (!tmp_errfile)
563 g_unlink(outf);
564 g_free(outf);
565 return NULL;
568 command = g_strdup_printf("%s %s >%s 2>%s",
569 cmd, inf, outf, tmp_errfile);
570 #ifdef TM_DEBUG
571 g_message("Executing: %s", command);
572 #endif
573 ret = system(command);
574 g_free(command);
576 g_file_get_contents(tmp_errfile, &errors, NULL, NULL);
577 if (errors && *errors)
578 g_printerr("%s", errors);
579 g_free(errors);
580 g_unlink(tmp_errfile);
581 g_free(tmp_errfile);
583 if (ret == -1)
585 g_unlink(outf);
586 g_free(outf);
587 return NULL;
590 return outf;
593 /* Creates a list of global tags. Ideally, this should be created once during
594 installations so that all users can use the same file. This is because a full
595 scale global tag list can occupy several megabytes of disk space.
596 @param pre_process The pre-processing command. This is executed via system(),
597 so you can pass stuff like 'gcc -E -dD -P `gnome-config --cflags gnome`'.
598 @param includes Include files to process. Wildcards such as '/usr/include/a*.h'
599 are allowed.
600 @param tags_file The file where the tags will be stored.
601 @param lang The language to use for the tags file.
602 @return TRUE on success, FALSE on failure.
604 gboolean tm_workspace_create_global_tags(const char *pre_process, const char **includes,
605 int includes_count, const char *tags_file, TMParserType lang)
607 gboolean ret = FALSE;
608 TMSourceFile *source_file;
609 GList *includes_files;
610 gchar *temp_file = create_temp_file("tmp_XXXXXX.cpp");
612 if (!temp_file)
613 return FALSE;
615 includes_files = lookup_includes(includes, includes_count);
617 #ifdef TM_DEBUG
618 g_message ("writing out files to %s\n", temp_file);
619 #endif
620 if (pre_process)
621 ret = write_includes_file(temp_file, includes_files);
622 else
623 ret = combine_include_files(temp_file, includes_files);
625 g_list_free_full(includes_files, g_free);
626 if (!ret)
627 goto cleanup;
628 ret = FALSE;
630 if (pre_process)
632 gchar *temp_file2 = pre_process_file(pre_process, temp_file);
634 if (temp_file2)
636 g_unlink(temp_file);
637 g_free(temp_file);
638 temp_file = temp_file2;
640 else
641 goto cleanup;
644 source_file = tm_source_file_new(temp_file, tm_source_file_get_lang_name(lang));
645 if (!source_file)
646 goto cleanup;
647 update_source_file(source_file, NULL, 0, FALSE, FALSE);
648 if (source_file->tags_array->len == 0)
650 tm_source_file_free(source_file);
651 goto cleanup;
654 tm_tags_sort(source_file->tags_array, global_tags_sort_attrs, TRUE, FALSE);
655 ret = tm_source_file_write_tags_file(tags_file, source_file->tags_array);
656 tm_source_file_free(source_file);
658 cleanup:
659 g_unlink(temp_file);
660 g_free(temp_file);
661 return ret;
665 static void fill_find_tags_array(GPtrArray *dst, const GPtrArray *src,
666 const char *name, const char *scope, TMTagType type, TMParserType lang)
668 TMTag **tag;
669 guint i, num;
671 if (!src || !dst || !name || !*name)
672 return;
674 tag = tm_tags_find(src, name, FALSE, &num);
675 for (i = 0; i < num; ++i)
677 if ((type & (*tag)->type) &&
678 tm_tag_langs_compatible(lang, (*tag)->lang) &&
679 (!scope || g_strcmp0((*tag)->scope, scope) == 0))
681 g_ptr_array_add(dst, *tag);
683 tag++;
688 /* Returns all matching tags found in the workspace.
689 @param name The name of the tag to find.
690 @param scope The scope name of the tag to find, or NULL.
691 @param type The tag types to return (TMTagType). Can be a bitmask.
692 @param attrs The attributes to sort and dedup on (0 terminated integer array).
693 @param lang Specifies the language(see the table in parsers.h) of the tags to be found,
694 -1 for all
695 @return Array of matching tags.
697 GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type,
698 TMTagAttrType *attrs, TMParserType lang)
700 GPtrArray *tags = g_ptr_array_new();
702 fill_find_tags_array(tags, theWorkspace->tags_array, name, scope, type, lang);
703 fill_find_tags_array(tags, theWorkspace->global_tags, name, scope, type, lang);
705 if (attrs)
706 tm_tags_sort(tags, attrs, TRUE, FALSE);
708 return tags;
712 static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src,
713 const char *name, TMParserType lang, guint max_num)
715 TMTag **tag, *last = NULL;
716 guint i, count, num;
718 if (!src || !dst || !name || !*name)
719 return;
721 num = 0;
722 tag = tm_tags_find(src, name, TRUE, &count);
723 for (i = 0; i < count && num < max_num; ++i)
725 if (tm_tag_langs_compatible(lang, (*tag)->lang) &&
726 !tm_tag_is_anon(*tag) &&
727 (!last || g_strcmp0(last->name, (*tag)->name) != 0))
729 g_ptr_array_add(dst, *tag);
730 last = *tag;
731 num++;
733 tag++;
738 /* Returns tags with the specified prefix sorted by name. If there are several
739 tags with the same name, only one of them appears in the resulting array.
740 @param prefix The prefix of the tag to find.
741 @param lang Specifies the language(see the table in parsers.h) of the tags to be found,
742 -1 for all.
743 @param max_num The maximum number of tags to return.
744 @return Array of matching tags sorted by their name.
746 GPtrArray *tm_workspace_find_prefix(const char *prefix, TMParserType lang, guint max_num)
748 TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 };
749 GPtrArray *tags = g_ptr_array_new();
751 fill_find_tags_array_prefix(tags, theWorkspace->tags_array, prefix, lang, max_num);
752 fill_find_tags_array_prefix(tags, theWorkspace->global_tags, prefix, lang, max_num);
754 tm_tags_sort(tags, attrs, TRUE, FALSE);
755 if (tags->len > max_num)
756 tags->len = max_num;
758 return tags;
762 /* Gets all members of type_tag; search them inside the all array.
763 * The namespace parameter determines whether we are performing the "namespace"
764 * search (user has typed something like "A::" where A is a type) or "scope" search
765 * (user has typed "a." where a is a global struct-like variable). With the
766 * namespace search we return all direct descendants of any type while with the
767 * scope search we return only those which can be invoked on a variable (member,
768 * method, etc.). */
769 static GPtrArray *
770 find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespace)
772 TMTagType member_types = tm_tag_max_t & ~(TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t);
773 GPtrArray *tags = g_ptr_array_new();
774 gchar *scope;
775 guint i;
777 if (namespace)
778 member_types = tm_tag_max_t;
780 if (type_tag->scope && *(type_tag->scope))
781 scope = g_strconcat(type_tag->scope, tm_tag_context_separator(type_tag->lang), type_tag->name, NULL);
782 else
783 scope = g_strdup(type_tag->name);
785 for (i = 0; i < all->len; ++i)
787 TMTag *tag = TM_TAG (all->pdata[i]);
789 if (tag && (tag->type & member_types) &&
790 tag->scope && tag->scope[0] != '\0' &&
791 tm_tag_langs_compatible(tag->lang, type_tag->lang) &&
792 strcmp(scope, tag->scope) == 0 &&
793 (!namespace || !tm_tag_is_anon(tag)))
795 g_ptr_array_add (tags, tag);
799 g_free(scope);
801 if (tags->len == 0)
803 g_ptr_array_free(tags, TRUE);
804 return NULL;
807 return tags;
811 static gchar *strip_type(const gchar *scoped_name, TMParserType lang)
813 if (scoped_name != NULL)
815 /* remove scope prefix */
816 const gchar *sep = tm_tag_context_separator(lang);
817 const gchar *base = g_strrstr(scoped_name, sep);
818 gchar *name = base ? g_strdup(base + strlen(sep)) : g_strdup(scoped_name);
820 /* remove pointers */
821 g_strdelimit(name, "*^", ' ');
822 g_strstrip(name);
824 return name;
826 return NULL;
830 /* Gets all members of the type with the given name; search them inside tags_array */
831 static GPtrArray *
832 find_scope_members (const GPtrArray *tags_array, const gchar *name, TMSourceFile *file,
833 TMParserType lang, gboolean namespace)
835 GPtrArray *res = NULL;
836 gchar *type_name;
837 guint i;
839 g_return_val_if_fail(name && *name, NULL);
841 type_name = g_strdup(name);
843 /* Check if type_name is a type that can possibly contain members.
844 * Try to resolve intermediate typedefs to get the real type name. Also
845 * add scope information to the name if applicable.
846 * The loop below loops only when resolving typedefs - avoid possibly infinite
847 * loop when typedefs create a cycle by adding some limits. */
848 for (i = 0; i < 5; i++)
850 guint j;
851 GPtrArray *type_tags;
852 TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t;
853 TMTag *tag = NULL;
855 if (!namespace)
856 types &= ~tm_tag_enum_t;
858 type_tags = g_ptr_array_new();
859 fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, lang);
861 for (j = 0; j < type_tags->len; j++)
863 TMTag *test_tag = TM_TAG(type_tags->pdata[j]);
865 /* anonymous type defined in a different file than the variable -
866 * this isn't the type we are looking for */
867 if (tm_tag_is_anon(test_tag) && (file != test_tag->file || test_tag->file == NULL))
868 continue;
870 tag = test_tag;
872 /* prefer non-typedef tags because we can be sure they contain members */
873 if (test_tag->type != tm_tag_typedef_t)
874 break;
877 g_ptr_array_free(type_tags, TRUE);
879 if (!tag) /* not a type that can contain members */
880 break;
882 /* intermediate typedef - resolve to the real type */
883 if (tag->type == tm_tag_typedef_t)
885 if (tag->var_type && tag->var_type[0] != '\0')
887 g_free(type_name);
888 type_name = strip_type(tag->var_type, tag->lang);
889 file = tag->file;
890 continue;
892 break;
894 else /* real type with members */
896 /* use the same file as the composite type if file information available */
897 res = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace);
898 break;
902 g_free(type_name);
904 return res;
908 /* Checks whether a member tag is directly accessible from method with method_scope */
909 static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *method_scope, TMTag *member_tag,
910 TMParserType lang)
912 const gchar *sep = tm_tag_context_separator(lang);
913 gboolean ret = FALSE;
914 gchar **comps;
915 guint len;
917 /* method scope is in the form ...::class_name::method_name */
918 comps = g_strsplit (method_scope, sep, 0);
919 len = g_strv_length(comps);
920 if (len > 1)
922 gchar *method, *member_scope, *cls, *cls_scope;
924 /* get method/member scope */
925 method = comps[len - 1];
926 comps[len - 1] = NULL;
927 member_scope = g_strjoinv(sep, comps);
928 comps[len - 1] = method;
930 /* get class scope */
931 cls = comps[len - 2];
932 comps[len - 2] = NULL;
933 cls_scope = g_strjoinv(sep, comps);
934 comps[len - 2] = cls;
935 cls_scope = strlen(cls_scope) > 0 ? cls_scope : NULL;
937 /* check whether member inside the class */
938 if (g_strcmp0(member_tag->scope, member_scope) == 0)
940 const GPtrArray *src = member_tag->file ? member_tag->file->tags_array : tags;
941 GPtrArray *cls_tags = g_ptr_array_new();
943 /* check whether the class exists */
944 fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS | tm_tag_namespace_t, lang);
945 ret = cls_tags->len > 0;
946 g_ptr_array_free(cls_tags, TRUE);
949 g_free(cls_scope);
950 g_free(member_scope);
953 g_strfreev(comps);
954 return ret;
958 /* For an array of variable/type tags, find members inside the types */
959 static GPtrArray *
960 find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, TMParserType lang,
961 gboolean member, const gchar *current_scope)
963 GPtrArray *member_tags = NULL;
964 guint i;
966 /* there may be several variables/types with the same name - try each of them until
967 * we find something */
968 for (i = 0; i < tags->len && !member_tags; i++)
970 TMTag *tag = TM_TAG(tags->pdata[i]);
971 TMTagType member_types = tm_tag_member_t | tm_tag_field_t | tm_tag_method_t;
972 TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t;
974 if (tag->type & types) /* type: namespace search */
976 if (tag->type & tm_tag_typedef_t)
977 member_tags = find_scope_members(searched_array, tag->name, tag->file, lang, TRUE);
978 else
979 member_tags = find_scope_members_tags(tag->file ? tag->file->tags_array : searched_array,
980 tag, TRUE);
982 else if (tag->var_type) /* variable: scope search */
984 /* The question now is whether we should use member tags (such as
985 * tm_tag_field_t, tm_tag_member_t) or not. We want them if member==TRUE
986 * (which means user has typed something like foo.bar.) or if we are
987 * inside a method where foo is a class member, we want scope completion
988 * for foo. */
989 if (!(tag->type & member_types) || member ||
990 member_at_method_scope(tags, current_scope, tag, lang))
992 gchar *tag_type = strip_type(tag->var_type, tag->lang);
994 member_tags = find_scope_members(searched_array, tag_type, tag->file, lang, FALSE);
995 g_free(tag_type);
1000 return member_tags;
1004 static GPtrArray *find_namespace_members_all(const GPtrArray *tags, const GPtrArray *searched_array, TMParserType lang)
1006 GPtrArray *member_tags = NULL;
1007 guint i;
1009 for (i = 0; i < tags->len && !member_tags; i++)
1011 TMTag *tag = TM_TAG(tags->pdata[i]);
1013 member_tags = find_scope_members_tags(searched_array, tag, TRUE);
1016 return member_tags;
1020 /* Returns all member tags of a struct/union/class if the provided name is a variable
1021 of such a type or the name of the type.
1022 @param source_file TMSourceFile of the edited source file
1023 @param name Name of the variable/type whose members are searched
1024 @param function TRUE if the name is a name of a function
1025 @param member TRUE if invoked on class/struct member (e.g. after the last dot in foo.bar.)
1026 @param current_scope The current scope in the editor
1027 @param search_namespace Whether to search the contents of namespace (e.g. after MyNamespace::)
1028 @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */
1029 GPtrArray *
1030 tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name,
1031 gboolean function, gboolean member, const gchar *current_scope, gboolean search_namespace)
1033 TMParserType lang = source_file ? source_file->lang : TM_PARSER_NONE;
1034 GPtrArray *tags, *member_tags = NULL;
1035 TMTagType function_types = tm_tag_function_t | tm_tag_method_t |
1036 tm_tag_macro_with_arg_t | tm_tag_prototype_t;
1037 TMTagType tag_type = tm_tag_max_t &
1038 ~(function_types | tm_tag_enumerator_t | tm_tag_namespace_t | tm_tag_package_t);
1039 TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, 0};
1041 if (search_namespace)
1043 tags = tm_workspace_find(name, NULL, tm_tag_namespace_t, NULL, lang);
1045 member_tags = find_namespace_members_all(tags, theWorkspace->tags_array, lang);
1046 if (!member_tags)
1047 member_tags = find_namespace_members_all(tags, theWorkspace->global_tags, lang);
1049 g_ptr_array_free(tags, TRUE);
1052 if (!member_tags)
1054 if (function)
1055 tag_type = function_types;
1057 /* tags corresponding to the variable/type name */
1058 tags = tm_workspace_find(name, NULL, tag_type, NULL, lang);
1060 /* Start searching inside the source file, continue with workspace tags and
1061 * end with global tags. This way we find the "closest" tag to the current
1062 * file in case there are more of them. */
1063 if (source_file)
1064 member_tags = find_scope_members_all(tags, source_file->tags_array,
1065 lang, member, current_scope);
1066 if (!member_tags)
1067 member_tags = find_scope_members_all(tags, theWorkspace->tags_array, lang,
1068 member, current_scope);
1069 if (!member_tags)
1070 member_tags = find_scope_members_all(tags, theWorkspace->global_tags, lang,
1071 member, current_scope);
1073 g_ptr_array_free(tags, TRUE);
1076 tm_tags_dedup(member_tags, sort_attr, FALSE);
1078 return member_tags;
1082 #ifdef TM_DEBUG
1084 /* Dumps the workspace tree - useful for debugging */
1085 void tm_workspace_dump(void)
1087 guint i;
1089 #ifdef TM_DEBUG
1090 g_message("Dumping TagManager workspace tree..");
1091 #endif
1092 for (i=0; i < theWorkspace->source_files->len; ++i)
1094 TMSourceFile *source_file = theWorkspace->source_files->pdata[i];
1095 fprintf(stderr, "%s", source_file->file_name);
1098 #endif /* TM_DEBUG */
1101 #if 0
1103 /* Returns a list of parent classes for the given class name
1104 @param name Name of the class
1105 @return A GPtrArray of TMTag pointers (includes the TMTag for the class) */
1106 static const GPtrArray *tm_workspace_get_parents(const gchar *name)
1108 static TMTagAttrType type[] = { tm_tag_attr_name_t, tm_tag_attr_none_t };
1109 static GPtrArray *parents = NULL;
1110 const GPtrArray *matches;
1111 guint i = 0;
1112 guint j;
1113 gchar **klasses;
1114 gchar **klass;
1115 TMTag *tag;
1117 g_return_val_if_fail(name && isalpha(*name),NULL);
1119 if (NULL == parents)
1120 parents = g_ptr_array_new();
1121 else
1122 g_ptr_array_set_size(parents, 0);
1123 matches = tm_workspace_find(name, NULL, tm_tag_class_t, type, -1);
1124 if ((NULL == matches) || (0 == matches->len))
1125 return NULL;
1126 g_ptr_array_add(parents, matches->pdata[0]);
1127 while (i < parents->len)
1129 tag = TM_TAG(parents->pdata[i]);
1130 if ((NULL != tag->inheritance) && (isalpha(tag->inheritance[0])))
1132 klasses = g_strsplit(tag->inheritance, ",", 10);
1133 for (klass = klasses; (NULL != *klass); ++ klass)
1135 for (j=0; j < parents->len; ++j)
1137 if (0 == strcmp(*klass, TM_TAG(parents->pdata[j])->name))
1138 break;
1140 if (parents->len == j)
1142 matches = tm_workspace_find(*klass, NULL, tm_tag_class_t, type, -1);
1143 if ((NULL != matches) && (0 < matches->len))
1144 g_ptr_array_add(parents, matches->pdata[0]);
1147 g_strfreev(klasses);
1149 ++ i;
1151 return parents;
1154 #endif