Use G_BEGIN_DECLS/G_END_DECLS
[geany-mirror.git] / tagmanager / src / tm_workspace.c
blob7e3548eb357d30eef0e0690bead0be69d3f6410b
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();
76 tm_source_file_ctags_init();
78 return TRUE;
82 /* Frees the workspace structure and all child source files. Use only when
83 exiting from the main program.
85 void tm_workspace_free(void)
87 guint i;
89 #ifdef TM_DEBUG
90 g_message("Workspace destroyed");
91 #endif
93 for (i=0; i < theWorkspace->source_files->len; ++i)
94 tm_source_file_free(theWorkspace->source_files->pdata[i]);
95 g_ptr_array_free(theWorkspace->source_files, TRUE);
96 tm_tags_array_free(theWorkspace->global_tags, TRUE);
97 g_ptr_array_free(theWorkspace->tags_array, TRUE);
98 g_ptr_array_free(theWorkspace->typename_array, TRUE);
99 g_ptr_array_free(theWorkspace->global_typename_array, TRUE);
100 g_free(theWorkspace);
101 theWorkspace = NULL;
105 /* Since TMWorkspace is a singleton, you should not create multiple
106 workspaces, but get a pointer to the workspace whenever required. The first
107 time a pointer is requested, or a source file is added to the workspace,
108 a workspace is created. Subsequent calls to the function will return the
109 created workspace.
111 const TMWorkspace *tm_get_workspace(void)
113 if (NULL == theWorkspace)
114 tm_create_workspace();
115 return theWorkspace;
119 static void tm_workspace_merge_tags(GPtrArray **big_array, GPtrArray *small_array)
121 GPtrArray *new_tags = tm_tags_merge(*big_array, small_array, workspace_tags_sort_attrs, FALSE);
122 /* tags owned by TMSourceFile - free just the pointer array */
123 g_ptr_array_free(*big_array, TRUE);
124 *big_array = new_tags;
128 static void merge_extracted_tags(GPtrArray **dest, GPtrArray *src, TMTagType tag_types)
130 GPtrArray *arr;
132 arr = tm_tags_extract(src, tag_types);
133 tm_workspace_merge_tags(dest, arr);
134 g_ptr_array_free(arr, TRUE);
138 static void update_source_file(TMSourceFile *source_file, guchar* text_buf,
139 gsize buf_size, gboolean use_buffer, gboolean update_workspace)
141 #ifdef TM_DEBUG
142 g_message("Source file updating based on source file %s", source_file->file_name);
143 #endif
145 if (update_workspace)
147 /* tm_source_file_parse() deletes the tag objects - remove the tags from
148 * workspace while they exist and can be scanned */
149 tm_tags_remove_file_tags(source_file, theWorkspace->tags_array);
150 tm_tags_remove_file_tags(source_file, theWorkspace->typename_array);
152 tm_source_file_parse(source_file, text_buf, buf_size, use_buffer);
153 tm_tags_sort(source_file->tags_array, file_tags_sort_attrs, FALSE, TRUE);
154 if (update_workspace)
156 #ifdef TM_DEBUG
157 g_message("Updating workspace from source file");
158 #endif
159 tm_workspace_merge_tags(&theWorkspace->tags_array, source_file->tags_array);
161 merge_extracted_tags(&(theWorkspace->typename_array), source_file->tags_array, TM_GLOBAL_TYPE_MASK);
163 #ifdef TM_DEBUG
164 else
165 g_message("Skipping workspace update because update_workspace is %s",
166 update_workspace?"TRUE":"FALSE");
168 #endif
172 /** Adds a source file to the workspace, parses it and updates the workspace tags.
173 @param source_file The source file to add to the workspace.
175 GEANY_API_SYMBOL
176 void tm_workspace_add_source_file(TMSourceFile *source_file)
178 g_return_if_fail(source_file != NULL);
180 g_ptr_array_add(theWorkspace->source_files, source_file);
181 update_source_file(source_file, NULL, 0, FALSE, TRUE);
185 void tm_workspace_add_source_file_noupdate(TMSourceFile *source_file)
187 g_return_if_fail(source_file != NULL);
189 g_ptr_array_add(theWorkspace->source_files, source_file);
193 /* Updates the source file by reparsing the text-buffer passed as parameter.
194 Ctags will use a parsing based on buffer instead of on files.
195 You should call this function when you don't want a previous saving of the file
196 you're editing. It's useful for a "real-time" updating of the tags.
197 The tags array and the tags themselves are destroyed and re-created, hence any
198 other tag arrays pointing to these tags should be rebuilt as well. All sorting
199 information is also lost.
200 @param source_file The source file to update with a buffer.
201 @param text_buf A text buffer. The user should take care of allocate and free it after
202 the use here.
203 @param buf_size The size of text_buf.
205 void tm_workspace_update_source_file_buffer(TMSourceFile *source_file, guchar* text_buf,
206 gsize buf_size)
208 update_source_file(source_file, text_buf, buf_size, TRUE, TRUE);
212 /** Removes a source file from the workspace if it exists. This function also removes
213 the tags belonging to this file from the workspace. To completely free the TMSourceFile
214 pointer call tm_source_file_free() on it.
215 @param source_file Pointer to the source file to be removed.
217 GEANY_API_SYMBOL
218 void tm_workspace_remove_source_file(TMSourceFile *source_file)
220 guint i;
222 g_return_if_fail(source_file != NULL);
224 for (i=0; i < theWorkspace->source_files->len; ++i)
226 if (theWorkspace->source_files->pdata[i] == source_file)
228 tm_tags_remove_file_tags(source_file, theWorkspace->tags_array);
229 tm_tags_remove_file_tags(source_file, theWorkspace->typename_array);
230 g_ptr_array_remove_index_fast(theWorkspace->source_files, i);
231 return;
237 /* Recreates workspace tag array from all member TMSourceFile objects. Use if you
238 want to globally refresh the workspace. This function does not call tm_source_file_update()
239 which should be called before this function on source files which need to be
240 reparsed.
242 static void tm_workspace_update(void)
244 guint i, j;
245 TMSourceFile *source_file;
247 #ifdef TM_DEBUG
248 g_message("Recreating workspace tags array");
249 #endif
251 g_ptr_array_set_size(theWorkspace->tags_array, 0);
253 #ifdef TM_DEBUG
254 g_message("Total %d objects", theWorkspace->source_files->len);
255 #endif
256 for (i=0; i < theWorkspace->source_files->len; ++i)
258 source_file = theWorkspace->source_files->pdata[i];
259 #ifdef TM_DEBUG
260 g_message("Adding tags of %s", source_file->file_name);
261 #endif
262 if (source_file->tags_array->len > 0)
264 for (j = 0; j < source_file->tags_array->len; ++j)
266 g_ptr_array_add(theWorkspace->tags_array,
267 source_file->tags_array->pdata[j]);
271 #ifdef TM_DEBUG
272 g_message("Total: %d tags", theWorkspace->tags_array->len);
273 #endif
274 tm_tags_sort(theWorkspace->tags_array, workspace_tags_sort_attrs, TRUE, FALSE);
276 g_ptr_array_free(theWorkspace->typename_array, TRUE);
277 theWorkspace->typename_array = tm_tags_extract(theWorkspace->tags_array, TM_GLOBAL_TYPE_MASK);
281 /** Adds multiple source files to the workspace and updates the workspace tag arrays.
282 This is more efficient than calling tm_workspace_add_source_file() and
283 tm_workspace_update_source_file() separately for each of the files.
284 @param source_files @elementtype{TMSourceFile} The source files to be added to the workspace.
286 GEANY_API_SYMBOL
287 void tm_workspace_add_source_files(GPtrArray *source_files)
289 guint i;
291 g_return_if_fail(source_files != NULL);
293 for (i = 0; i < source_files->len; i++)
295 TMSourceFile *source_file = source_files->pdata[i];
297 tm_workspace_add_source_file_noupdate(source_file);
298 update_source_file(source_file, NULL, 0, FALSE, FALSE);
301 tm_workspace_update();
305 /** Removes multiple source files from the workspace and updates the workspace tag
306 arrays. This is more efficient than calling tm_workspace_remove_source_file()
307 separately for each of the files. To completely free the TMSourceFile pointers
308 call tm_source_file_free() on each of them.
309 @param source_files @elementtype{TMSourceFile} The source files to be removed from the workspace.
311 GEANY_API_SYMBOL
312 void tm_workspace_remove_source_files(GPtrArray *source_files)
314 guint i, j;
316 g_return_if_fail(source_files != NULL);
318 //TODO: sort both arrays by pointer value and remove in single pass
319 for (i = 0; i < source_files->len; i++)
321 TMSourceFile *source_file = source_files->pdata[i];
323 for (j = 0; j < theWorkspace->source_files->len; j++)
325 if (theWorkspace->source_files->pdata[j] == source_file)
327 g_ptr_array_remove_index_fast(theWorkspace->source_files, j);
328 break;
333 tm_workspace_update();
337 /* Loads the global tag list from the specified file. The global tag list should
338 have been first created using tm_workspace_create_global_tags().
339 @param tags_file The file containing global tags.
340 @return TRUE on success, FALSE on failure.
341 @see tm_workspace_create_global_tags()
343 gboolean tm_workspace_load_global_tags(const char *tags_file, TMParserType mode)
345 guchar buf[BUFSIZ];
346 FILE *fp;
347 GPtrArray *file_tags, *new_tags;
348 TMTag *tag;
349 TMFileFormat format = TM_FILE_FORMAT_TAGMANAGER;
351 if (NULL == (fp = g_fopen(tags_file, "r")))
352 return FALSE;
353 if ((NULL == fgets((gchar*) buf, BUFSIZ, fp)) || ('\0' == *buf))
355 fclose(fp);
356 return FALSE; /* early out on error */
358 else
359 { /* We read the first line for the format specification. */
360 if (buf[0] == '#' && strstr((gchar*) buf, "format=pipe") != NULL)
361 format = TM_FILE_FORMAT_PIPE;
362 else if (buf[0] == '#' && strstr((gchar*) buf, "format=tagmanager") != NULL)
363 format = TM_FILE_FORMAT_TAGMANAGER;
364 else if (buf[0] == '#' && strstr((gchar*) buf, "format=ctags") != NULL)
365 format = TM_FILE_FORMAT_CTAGS;
366 else if (strncmp((gchar*) buf, "!_TAG_", 6) == 0)
367 format = TM_FILE_FORMAT_CTAGS;
368 else
369 { /* We didn't find a valid format specification, so we try to auto-detect the format
370 * by counting the pipe characters on the first line and asumme pipe format when
371 * we find more than one pipe on the line. */
372 guint i, pipe_cnt = 0, tab_cnt = 0;
373 for (i = 0; i < BUFSIZ && buf[i] != '\0' && pipe_cnt < 2; i++)
375 if (buf[i] == '|')
376 pipe_cnt++;
377 else if (buf[i] == '\t')
378 tab_cnt++;
380 if (pipe_cnt > 1)
381 format = TM_FILE_FORMAT_PIPE;
382 else if (tab_cnt > 1)
383 format = TM_FILE_FORMAT_CTAGS;
385 rewind(fp); /* reset the file pointer, to start reading again from the beginning */
388 file_tags = g_ptr_array_new();
389 while (NULL != (tag = tm_tag_new_from_file(NULL, fp, mode, format)))
390 g_ptr_array_add(file_tags, tag);
391 fclose(fp);
393 tm_tags_sort(file_tags, global_tags_sort_attrs, TRUE, TRUE);
395 /* reorder the whole array, because tm_tags_find expects a sorted array */
396 new_tags = tm_tags_merge(theWorkspace->global_tags,
397 file_tags, global_tags_sort_attrs, TRUE);
398 g_ptr_array_free(theWorkspace->global_tags, TRUE);
399 g_ptr_array_free(file_tags, TRUE);
400 theWorkspace->global_tags = new_tags;
402 g_ptr_array_free(theWorkspace->global_typename_array, TRUE);
403 theWorkspace->global_typename_array = tm_tags_extract(new_tags, TM_GLOBAL_TYPE_MASK);
405 return TRUE;
409 static guint tm_file_inode_hash(gconstpointer key)
411 GStatBuf file_stat;
412 const char *filename = (const char*)key;
413 if (g_stat(filename, &file_stat) == 0)
415 #ifdef TM_DEBUG
416 g_message ("Hash for '%s' is '%d'\n", filename, file_stat.st_ino);
417 #endif
418 return g_direct_hash ((gpointer)(intptr_t)file_stat.st_ino);
419 } else {
420 return 0;
425 static void tm_move_entries_to_g_list(gpointer key, gpointer value, gpointer user_data)
427 GList **pp_list = (GList**)user_data;
429 if (user_data == NULL)
430 return;
432 *pp_list = g_list_prepend(*pp_list, value);
436 static void write_includes_file(FILE *fp, GList *includes_files)
438 GList *node;
440 node = includes_files;
441 while (node)
443 char *str = g_strdup_printf("#include \"%s\"\n", (char*)node->data);
444 size_t str_len = strlen(str);
446 fwrite(str, str_len, 1, fp);
447 g_free(str);
448 node = g_list_next(node);
453 static void append_to_temp_file(FILE *fp, GList *file_list)
455 GList *node;
457 node = file_list;
458 while (node)
460 const char *fname = node->data;
461 char *contents;
462 size_t length;
463 GError *err = NULL;
465 if (! g_file_get_contents(fname, &contents, &length, &err))
467 fprintf(stderr, "Unable to read file: %s\n", err->message);
468 g_error_free(err);
470 else
472 fwrite(contents, length, 1, fp);
473 fwrite("\n", 1, 1, fp); /* in case file doesn't end in newline (e.g. windows). */
474 g_free(contents);
476 node = g_list_next (node);
481 static gchar *create_temp_file(const gchar *tpl)
483 gchar *name;
484 gint fd;
486 fd = g_file_open_tmp(tpl, &name, NULL);
487 if (fd < 0)
488 name = NULL;
489 else
490 close(fd);
492 return name;
496 /* Creates a list of global tags. Ideally, this should be created once during
497 installations so that all users can use the same file. This is because a full
498 scale global tag list can occupy several megabytes of disk space.
499 @param pre_process The pre-processing command. This is executed via system(),
500 so you can pass stuff like 'gcc -E -dD -P `gnome-config --cflags gnome`'.
501 @param includes Include files to process. Wildcards such as '/usr/include/a*.h'
502 are allowed.
503 @param tags_file The file where the tags will be stored.
504 @param lang The language to use for the tags file.
505 @return TRUE on success, FALSE on failure.
507 gboolean tm_workspace_create_global_tags(const char *pre_process, const char **includes,
508 int includes_count, const char *tags_file, TMParserType lang)
510 #ifdef HAVE_GLOB_H
511 glob_t globbuf;
512 size_t idx_glob;
513 #endif
514 int idx_inc;
515 char *command;
516 guint i;
517 FILE *fp;
518 TMSourceFile *source_file;
519 GPtrArray *tags_array;
520 GHashTable *includes_files_hash;
521 GList *includes_files = NULL;
522 gchar *temp_file = create_temp_file("tmp_XXXXXX.cpp");
523 gchar *temp_file2 = create_temp_file("tmp_XXXXXX.cpp");
525 if (NULL == temp_file || NULL == temp_file2 ||
526 NULL == (fp = g_fopen(temp_file, "w")))
528 g_free(temp_file);
529 g_free(temp_file2);
530 return FALSE;
533 includes_files_hash = g_hash_table_new_full (tm_file_inode_hash,
534 g_direct_equal,
535 NULL, g_free);
537 #ifdef HAVE_GLOB_H
538 globbuf.gl_offs = 0;
540 if (includes[0][0] == '"') /* leading \" char for glob matching */
541 for(idx_inc = 0; idx_inc < includes_count; idx_inc++)
543 size_t dirty_len = strlen(includes[idx_inc]);
544 char *clean_path = g_malloc(dirty_len - 1);
546 strncpy(clean_path, includes[idx_inc] + 1, dirty_len - 1);
547 clean_path[dirty_len - 2] = 0;
549 #ifdef TM_DEBUG
550 g_message ("[o][%s]\n", clean_path);
551 #endif
552 glob(clean_path, 0, NULL, &globbuf);
554 #ifdef TM_DEBUG
555 g_message ("matches: %d\n", globbuf.gl_pathc);
556 #endif
558 for(idx_glob = 0; idx_glob < globbuf.gl_pathc; idx_glob++)
560 #ifdef TM_DEBUG
561 g_message (">>> %s\n", globbuf.gl_pathv[idx_glob]);
562 #endif
563 if (!g_hash_table_lookup(includes_files_hash,
564 globbuf.gl_pathv[idx_glob]))
566 char* file_name_copy = strdup(globbuf.gl_pathv[idx_glob]);
567 g_hash_table_insert(includes_files_hash, file_name_copy,
568 file_name_copy);
569 #ifdef TM_DEBUG
570 g_message ("Added ...\n");
571 #endif
574 globfree(&globbuf);
575 g_free(clean_path);
577 else
578 #endif
579 /* no glob support or globbing not wanted */
580 for(idx_inc = 0; idx_inc < includes_count; idx_inc++)
582 if (!g_hash_table_lookup(includes_files_hash,
583 includes[idx_inc]))
585 char* file_name_copy = strdup(includes[idx_inc]);
586 g_hash_table_insert(includes_files_hash, file_name_copy,
587 file_name_copy);
591 /* Checks for duplicate file entries which would case trouble */
592 g_hash_table_foreach(includes_files_hash, tm_move_entries_to_g_list,
593 &includes_files);
595 includes_files = g_list_reverse (includes_files);
597 #ifdef TM_DEBUG
598 g_message ("writing out files to %s\n", temp_file);
599 #endif
600 if (pre_process != NULL)
601 write_includes_file(fp, includes_files);
602 else
603 append_to_temp_file(fp, includes_files);
605 g_list_free (includes_files);
606 g_hash_table_destroy(includes_files_hash);
607 includes_files_hash = NULL;
608 includes_files = NULL;
609 fclose(fp);
611 if (pre_process != NULL)
613 gint ret;
614 gchar *tmp_errfile = create_temp_file("tmp_XXXXXX");
615 gchar *errors = NULL;
616 command = g_strdup_printf("%s %s >%s 2>%s",
617 pre_process, temp_file, temp_file2, tmp_errfile);
618 #ifdef TM_DEBUG
619 g_message("Executing: %s", command);
620 #endif
621 ret = system(command);
622 g_free(command);
623 g_unlink(temp_file);
624 g_free(temp_file);
625 g_file_get_contents(tmp_errfile, &errors, NULL, NULL);
626 if (errors && *errors)
627 g_printerr("%s", errors);
628 g_free(errors);
629 g_unlink(tmp_errfile);
630 g_free(tmp_errfile);
631 if (ret == -1)
633 g_unlink(temp_file2);
634 return FALSE;
637 else
639 /* no pre-processing needed, so temp_file2 = temp_file */
640 g_unlink(temp_file2);
641 g_free(temp_file2);
642 temp_file2 = temp_file;
643 temp_file = NULL;
645 source_file = tm_source_file_new(temp_file2, tm_source_file_get_lang_name(lang));
646 update_source_file(source_file, NULL, 0, FALSE, FALSE);
647 if (NULL == source_file)
649 g_unlink(temp_file2);
650 return FALSE;
652 g_unlink(temp_file2);
653 g_free(temp_file2);
654 if (0 == source_file->tags_array->len)
656 tm_source_file_free(source_file);
657 return FALSE;
659 tags_array = tm_tags_extract(source_file->tags_array, tm_tag_max_t);
660 if ((NULL == tags_array) || (0 == tags_array->len))
662 if (tags_array)
663 g_ptr_array_free(tags_array, TRUE);
664 tm_source_file_free(source_file);
665 return FALSE;
667 if (FALSE == tm_tags_sort(tags_array, global_tags_sort_attrs, TRUE, FALSE))
669 tm_source_file_free(source_file);
670 return FALSE;
672 if (NULL == (fp = g_fopen(tags_file, "w")))
674 tm_source_file_free(source_file);
675 return FALSE;
677 fprintf(fp, "# format=tagmanager\n");
678 for (i = 0; i < tags_array->len; ++i)
680 tm_tag_write(TM_TAG(tags_array->pdata[i]), fp, tm_tag_attr_type_t
681 | tm_tag_attr_scope_t | tm_tag_attr_arglist_t | tm_tag_attr_vartype_t
682 | tm_tag_attr_pointer_t);
684 fclose(fp);
685 tm_source_file_free(source_file);
686 g_ptr_array_free(tags_array, TRUE);
687 return TRUE;
691 static void fill_find_tags_array(GPtrArray *dst, const GPtrArray *src,
692 const char *name, const char *scope, TMTagType type, TMParserType lang)
694 TMTag **tag;
695 guint i, num;
697 if (!src || !dst || !name || !*name)
698 return;
700 tag = tm_tags_find(src, name, FALSE, &num);
701 for (i = 0; i < num; ++i)
703 if ((type & (*tag)->type) &&
704 tm_tag_langs_compatible(lang, (*tag)->lang) &&
705 (!scope || g_strcmp0((*tag)->scope, scope) == 0))
707 g_ptr_array_add(dst, *tag);
709 tag++;
714 /* Returns all matching tags found in the workspace.
715 @param name The name of the tag to find.
716 @param scope The scope name of the tag to find, or NULL.
717 @param type The tag types to return (TMTagType). Can be a bitmask.
718 @param attrs The attributes to sort and dedup on (0 terminated integer array).
719 @param lang Specifies the language(see the table in parsers.h) of the tags to be found,
720 -1 for all
721 @return Array of matching tags.
723 GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type,
724 TMTagAttrType *attrs, TMParserType lang)
726 GPtrArray *tags = g_ptr_array_new();
728 fill_find_tags_array(tags, theWorkspace->tags_array, name, scope, type, lang);
729 fill_find_tags_array(tags, theWorkspace->global_tags, name, scope, type, lang);
731 if (attrs)
732 tm_tags_sort(tags, attrs, TRUE, FALSE);
734 return tags;
738 static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src,
739 const char *name, TMParserType lang, guint max_num)
741 TMTag **tag, *last = NULL;
742 guint i, count, num;
744 if (!src || !dst || !name || !*name)
745 return;
747 num = 0;
748 tag = tm_tags_find(src, name, TRUE, &count);
749 for (i = 0; i < count && num < max_num; ++i)
751 if (tm_tag_langs_compatible(lang, (*tag)->lang) &&
752 !tm_tag_is_anon(*tag) &&
753 (!last || g_strcmp0(last->name, (*tag)->name) != 0))
755 g_ptr_array_add(dst, *tag);
756 last = *tag;
757 num++;
759 tag++;
764 /* Returns tags with the specified prefix sorted by name. If there are several
765 tags with the same name, only one of them appears in the resulting array.
766 @param prefix The prefix of the tag to find.
767 @param lang Specifies the language(see the table in parsers.h) of the tags to be found,
768 -1 for all.
769 @param max_num The maximum number of tags to return.
770 @return Array of matching tags sorted by their name.
772 GPtrArray *tm_workspace_find_prefix(const char *prefix, TMParserType lang, guint max_num)
774 TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 };
775 GPtrArray *tags = g_ptr_array_new();
777 fill_find_tags_array_prefix(tags, theWorkspace->tags_array, prefix, lang, max_num);
778 fill_find_tags_array_prefix(tags, theWorkspace->global_tags, prefix, lang, max_num);
780 tm_tags_sort(tags, attrs, TRUE, FALSE);
781 if (tags->len > max_num)
782 tags->len = max_num;
784 return tags;
788 /* Gets all members of type_tag; search them inside the all array.
789 * The namespace parameter determines whether we are performing the "namespace"
790 * search (user has typed something like "A::" where A is a type) or "scope" search
791 * (user has typed "a." where a is a global struct-like variable). With the
792 * namespace search we return all direct descendants of any type while with the
793 * scope search we return only those which can be invoked on a variable (member,
794 * method, etc.). */
795 static GPtrArray *
796 find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespace)
798 TMTagType member_types = tm_tag_max_t & ~(TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t);
799 GPtrArray *tags = g_ptr_array_new();
800 gchar *scope;
801 guint i;
803 if (namespace)
804 member_types = tm_tag_max_t;
806 if (type_tag->scope && *(type_tag->scope))
807 scope = g_strconcat(type_tag->scope, tm_tag_context_separator(type_tag->lang), type_tag->name, NULL);
808 else
809 scope = g_strdup(type_tag->name);
811 for (i = 0; i < all->len; ++i)
813 TMTag *tag = TM_TAG (all->pdata[i]);
815 if (tag && (tag->type & member_types) &&
816 tag->scope && tag->scope[0] != '\0' &&
817 tm_tag_langs_compatible(tag->lang, type_tag->lang) &&
818 strcmp(scope, tag->scope) == 0 &&
819 (!namespace || !tm_tag_is_anon(tag)))
821 g_ptr_array_add (tags, tag);
825 g_free(scope);
827 if (tags->len == 0)
829 g_ptr_array_free(tags, TRUE);
830 return NULL;
833 return tags;
837 static gchar *strip_type(const gchar *scoped_name, TMParserType lang)
839 if (scoped_name != NULL)
841 /* remove scope prefix */
842 const gchar *sep = tm_tag_context_separator(lang);
843 const gchar *base = g_strrstr(scoped_name, sep);
844 gchar *name = base ? g_strdup(base + strlen(sep)) : g_strdup(scoped_name);
846 /* remove pointers */
847 g_strdelimit(name, "*^", ' ');
848 g_strstrip(name);
850 return name;
852 return NULL;
856 /* Gets all members of the type with the given name; search them inside tags_array */
857 static GPtrArray *
858 find_scope_members (const GPtrArray *tags_array, const gchar *name, TMSourceFile *file,
859 TMParserType lang, gboolean namespace)
861 GPtrArray *res = NULL;
862 gchar *type_name;
863 guint i;
865 g_return_val_if_fail(name && *name, NULL);
867 type_name = g_strdup(name);
869 /* Check if type_name is a type that can possibly contain members.
870 * Try to resolve intermediate typedefs to get the real type name. Also
871 * add scope information to the name if applicable.
872 * The loop below loops only when resolving typedefs - avoid possibly infinite
873 * loop when typedefs create a cycle by adding some limits. */
874 for (i = 0; i < 5; i++)
876 guint j;
877 GPtrArray *type_tags;
878 TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t;
879 TMTag *tag = NULL;
881 if (!namespace)
882 types &= ~tm_tag_enum_t;
884 type_tags = g_ptr_array_new();
885 fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, lang);
887 for (j = 0; j < type_tags->len; j++)
889 TMTag *test_tag = TM_TAG(type_tags->pdata[j]);
891 /* anonymous type defined in a different file than the variable -
892 * this isn't the type we are looking for */
893 if (tm_tag_is_anon(test_tag) && (file != test_tag->file || test_tag->file == NULL))
894 continue;
896 tag = test_tag;
898 /* prefer non-typedef tags because we can be sure they contain members */
899 if (test_tag->type != tm_tag_typedef_t)
900 break;
903 g_ptr_array_free(type_tags, TRUE);
905 if (!tag) /* not a type that can contain members */
906 break;
908 /* intermediate typedef - resolve to the real type */
909 if (tag->type == tm_tag_typedef_t)
911 if (tag->var_type && tag->var_type[0] != '\0')
913 g_free(type_name);
914 type_name = strip_type(tag->var_type, tag->lang);
915 file = tag->file;
916 continue;
918 break;
920 else /* real type with members */
922 /* use the same file as the composite type if file information available */
923 res = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace);
924 break;
928 g_free(type_name);
930 return res;
934 /* Checks whether a member tag is directly accessible from method with method_scope */
935 static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *method_scope, TMTag *member_tag,
936 TMParserType lang)
938 const gchar *sep = tm_tag_context_separator(lang);
939 gboolean ret = FALSE;
940 gchar **comps;
941 guint len;
943 /* method scope is in the form ...::class_name::method_name */
944 comps = g_strsplit (method_scope, sep, 0);
945 len = g_strv_length(comps);
946 if (len > 1)
948 gchar *method, *member_scope, *cls, *cls_scope;
950 /* get method/member scope */
951 method = comps[len - 1];
952 comps[len - 1] = NULL;
953 member_scope = g_strjoinv(sep, comps);
954 comps[len - 1] = method;
956 /* get class scope */
957 cls = comps[len - 2];
958 comps[len - 2] = NULL;
959 cls_scope = g_strjoinv(sep, comps);
960 comps[len - 2] = cls;
961 cls_scope = strlen(cls_scope) > 0 ? cls_scope : NULL;
963 /* check whether member inside the class */
964 if (g_strcmp0(member_tag->scope, member_scope) == 0)
966 const GPtrArray *src = member_tag->file ? member_tag->file->tags_array : tags;
967 GPtrArray *cls_tags = g_ptr_array_new();
969 /* check whether the class exists */
970 fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS | tm_tag_namespace_t, lang);
971 ret = cls_tags->len > 0;
972 g_ptr_array_free(cls_tags, TRUE);
975 g_free(cls_scope);
976 g_free(member_scope);
979 g_strfreev(comps);
980 return ret;
984 /* For an array of variable/type tags, find members inside the types */
985 static GPtrArray *
986 find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, TMParserType lang,
987 gboolean member, const gchar *current_scope)
989 GPtrArray *member_tags = NULL;
990 guint i;
992 /* there may be several variables/types with the same name - try each of them until
993 * we find something */
994 for (i = 0; i < tags->len && !member_tags; i++)
996 TMTag *tag = TM_TAG(tags->pdata[i]);
997 TMTagType member_types = tm_tag_member_t | tm_tag_field_t | tm_tag_method_t;
998 TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t;
1000 if (tag->type & types) /* type: namespace search */
1002 if (tag->type & tm_tag_typedef_t)
1003 member_tags = find_scope_members(searched_array, tag->name, tag->file, lang, TRUE);
1004 else
1005 member_tags = find_scope_members_tags(tag->file ? tag->file->tags_array : searched_array,
1006 tag, TRUE);
1008 else if (tag->var_type) /* variable: scope search */
1010 /* The question now is whether we should use member tags (such as
1011 * tm_tag_field_t, tm_tag_member_t) or not. We want them if member==TRUE
1012 * (which means user has typed something like foo.bar.) or if we are
1013 * inside a method where foo is a class member, we want scope completion
1014 * for foo. */
1015 if (!(tag->type & member_types) || member ||
1016 member_at_method_scope(tags, current_scope, tag, lang))
1018 gchar *tag_type = strip_type(tag->var_type, tag->lang);
1020 member_tags = find_scope_members(searched_array, tag_type, tag->file, lang, FALSE);
1021 g_free(tag_type);
1026 return member_tags;
1030 static GPtrArray *find_namespace_members_all(const GPtrArray *tags, const GPtrArray *searched_array, TMParserType lang)
1032 GPtrArray *member_tags = NULL;
1033 guint i;
1035 for (i = 0; i < tags->len && !member_tags; i++)
1037 TMTag *tag = TM_TAG(tags->pdata[i]);
1039 member_tags = find_scope_members_tags(searched_array, tag, TRUE);
1042 return member_tags;
1046 /* Returns all member tags of a struct/union/class if the provided name is a variable
1047 of such a type or the name of the type.
1048 @param source_file TMSourceFile of the edited source file
1049 @param name Name of the variable/type whose members are searched
1050 @param function TRUE if the name is a name of a function
1051 @param member TRUE if invoked on class/struct member (e.g. after the last dot in foo.bar.)
1052 @param current_scope The current scope in the editor
1053 @param search_namespace Whether to search the contents of namespace (e.g. after MyNamespace::)
1054 @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */
1055 GPtrArray *
1056 tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name,
1057 gboolean function, gboolean member, const gchar *current_scope, gboolean search_namespace)
1059 TMParserType lang = source_file ? source_file->lang : TM_PARSER_NONE;
1060 GPtrArray *tags, *member_tags = NULL;
1061 TMTagType function_types = tm_tag_function_t | tm_tag_method_t |
1062 tm_tag_macro_with_arg_t | tm_tag_prototype_t;
1063 TMTagType tag_type = tm_tag_max_t &
1064 ~(function_types | tm_tag_enumerator_t | tm_tag_namespace_t | tm_tag_package_t);
1065 TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, 0};
1067 if (search_namespace)
1069 tags = tm_workspace_find(name, NULL, tm_tag_namespace_t, NULL, lang);
1071 member_tags = find_namespace_members_all(tags, theWorkspace->tags_array, lang);
1072 if (!member_tags)
1073 member_tags = find_namespace_members_all(tags, theWorkspace->global_tags, lang);
1075 g_ptr_array_free(tags, TRUE);
1078 if (!member_tags)
1080 if (function)
1081 tag_type = function_types;
1083 /* tags corresponding to the variable/type name */
1084 tags = tm_workspace_find(name, NULL, tag_type, NULL, lang);
1086 /* Start searching inside the source file, continue with workspace tags and
1087 * end with global tags. This way we find the "closest" tag to the current
1088 * file in case there are more of them. */
1089 if (source_file)
1090 member_tags = find_scope_members_all(tags, source_file->tags_array,
1091 lang, member, current_scope);
1092 if (!member_tags)
1093 member_tags = find_scope_members_all(tags, theWorkspace->tags_array, lang,
1094 member, current_scope);
1095 if (!member_tags)
1096 member_tags = find_scope_members_all(tags, theWorkspace->global_tags, lang,
1097 member, current_scope);
1099 g_ptr_array_free(tags, TRUE);
1102 tm_tags_dedup(member_tags, sort_attr, FALSE);
1104 return member_tags;
1108 #ifdef TM_DEBUG
1110 /* Dumps the workspace tree - useful for debugging */
1111 void tm_workspace_dump(void)
1113 guint i;
1115 #ifdef TM_DEBUG
1116 g_message("Dumping TagManager workspace tree..");
1117 #endif
1118 for (i=0; i < theWorkspace->source_files->len; ++i)
1120 TMSourceFile *source_file = theWorkspace->source_files->pdata[i];
1121 fprintf(stderr, "%s", source_file->file_name);
1124 #endif /* TM_DEBUG */
1127 #if 0
1129 /* Returns a list of parent classes for the given class name
1130 @param name Name of the class
1131 @return A GPtrArray of TMTag pointers (includes the TMTag for the class) */
1132 static const GPtrArray *tm_workspace_get_parents(const gchar *name)
1134 static TMTagAttrType type[] = { tm_tag_attr_name_t, tm_tag_attr_none_t };
1135 static GPtrArray *parents = NULL;
1136 const GPtrArray *matches;
1137 guint i = 0;
1138 guint j;
1139 gchar **klasses;
1140 gchar **klass;
1141 TMTag *tag;
1143 g_return_val_if_fail(name && isalpha(*name),NULL);
1145 if (NULL == parents)
1146 parents = g_ptr_array_new();
1147 else
1148 g_ptr_array_set_size(parents, 0);
1149 matches = tm_workspace_find(name, NULL, tm_tag_class_t, type, -1);
1150 if ((NULL == matches) || (0 == matches->len))
1151 return NULL;
1152 g_ptr_array_add(parents, matches->pdata[0]);
1153 while (i < parents->len)
1155 tag = TM_TAG(parents->pdata[i]);
1156 if ((NULL != tag->inheritance) && (isalpha(tag->inheritance[0])))
1158 klasses = g_strsplit(tag->inheritance, ",", 10);
1159 for (klass = klasses; (NULL != *klass); ++ klass)
1161 for (j=0; j < parents->len; ++j)
1163 if (0 == strcmp(*klass, TM_TAG(parents->pdata[j])->name))
1164 break;
1166 if (parents->len == j)
1168 matches = tm_workspace_find(*klass, NULL, tm_tag_class_t, type, -1);
1169 if ((NULL != matches) && (0 < matches->len))
1170 g_ptr_array_add(parents, matches->pdata[0]);
1173 g_strfreev(klasses);
1175 ++ i;
1177 return parents;
1180 #endif