Improve API docs related to keybindings configuration file
[geany-mirror.git] / tagmanager / src / tm_workspace.c
blob788299fcea3afb3f52e39efe9fc0b1fbbb28623e
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. The language parameter is automatically detected
197 the first time the file is parsed if it is set to LANG_AUTO.
198 @param source_file The source file to update with a buffer.
199 @param text_buf A text buffer. The user should take care of allocate and free it after
200 the use here.
201 @param buf_size The size of text_buf.
203 void tm_workspace_update_source_file_buffer(TMSourceFile *source_file, guchar* text_buf,
204 gsize buf_size)
206 update_source_file(source_file, text_buf, buf_size, TRUE, TRUE);
210 /** Removes a source file from the workspace if it exists. This function also removes
211 the tags belonging to this file from the workspace. To completely free the TMSourceFile
212 pointer call tm_source_file_free() on it.
213 @param source_file Pointer to the source file to be removed.
215 GEANY_API_SYMBOL
216 void tm_workspace_remove_source_file(TMSourceFile *source_file)
218 guint i;
220 g_return_if_fail(source_file != NULL);
222 for (i=0; i < theWorkspace->source_files->len; ++i)
224 if (theWorkspace->source_files->pdata[i] == source_file)
226 tm_tags_remove_file_tags(source_file, theWorkspace->tags_array);
227 tm_tags_remove_file_tags(source_file, theWorkspace->typename_array);
228 g_ptr_array_remove_index_fast(theWorkspace->source_files, i);
229 return;
235 /* Recreates workspace tag array from all member TMSourceFile objects. Use if you
236 want to globally refresh the workspace. This function does not call tm_source_file_update()
237 which should be called before this function on source files which need to be
238 reparsed.
240 static void tm_workspace_update(void)
242 guint i, j;
243 TMSourceFile *source_file;
245 #ifdef TM_DEBUG
246 g_message("Recreating workspace tags array");
247 #endif
249 g_ptr_array_set_size(theWorkspace->tags_array, 0);
251 #ifdef TM_DEBUG
252 g_message("Total %d objects", theWorkspace->source_files->len);
253 #endif
254 for (i=0; i < theWorkspace->source_files->len; ++i)
256 source_file = theWorkspace->source_files->pdata[i];
257 #ifdef TM_DEBUG
258 g_message("Adding tags of %s", source_file->file_name);
259 #endif
260 if (source_file->tags_array->len > 0)
262 for (j = 0; j < source_file->tags_array->len; ++j)
264 g_ptr_array_add(theWorkspace->tags_array,
265 source_file->tags_array->pdata[j]);
269 #ifdef TM_DEBUG
270 g_message("Total: %d tags", theWorkspace->tags_array->len);
271 #endif
272 tm_tags_sort(theWorkspace->tags_array, workspace_tags_sort_attrs, TRUE, FALSE);
274 g_ptr_array_free(theWorkspace->typename_array, TRUE);
275 theWorkspace->typename_array = tm_tags_extract(theWorkspace->tags_array, TM_GLOBAL_TYPE_MASK);
279 /** Adds multiple source files to the workspace and updates the workspace tag arrays.
280 This is more efficient than calling tm_workspace_add_source_file() and
281 tm_workspace_update_source_file() separately for each of the files.
282 @param source_files @elementtype{TMSourceFile} The source files to be added to the workspace.
284 GEANY_API_SYMBOL
285 void tm_workspace_add_source_files(GPtrArray *source_files)
287 guint i;
289 g_return_if_fail(source_files != NULL);
291 for (i = 0; i < source_files->len; i++)
293 TMSourceFile *source_file = source_files->pdata[i];
295 tm_workspace_add_source_file_noupdate(source_file);
296 update_source_file(source_file, NULL, 0, FALSE, FALSE);
299 tm_workspace_update();
303 /** Removes multiple source files from the workspace and updates the workspace tag
304 arrays. This is more efficient than calling tm_workspace_remove_source_file()
305 separately for each of the files. To completely free the TMSourceFile pointers
306 call tm_source_file_free() on each of them.
307 @param source_files @elementtype{TMSourceFile} The source files to be removed from the workspace.
309 GEANY_API_SYMBOL
310 void tm_workspace_remove_source_files(GPtrArray *source_files)
312 guint i, j;
314 g_return_if_fail(source_files != NULL);
316 //TODO: sort both arrays by pointer value and remove in single pass
317 for (i = 0; i < source_files->len; i++)
319 TMSourceFile *source_file = source_files->pdata[i];
321 for (j = 0; j < theWorkspace->source_files->len; j++)
323 if (theWorkspace->source_files->pdata[j] == source_file)
325 g_ptr_array_remove_index_fast(theWorkspace->source_files, j);
326 break;
331 tm_workspace_update();
335 /* Loads the global tag list from the specified file. The global tag list should
336 have been first created using tm_workspace_create_global_tags().
337 @param tags_file The file containing global tags.
338 @return TRUE on success, FALSE on failure.
339 @see tm_workspace_create_global_tags()
341 gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode)
343 guchar buf[BUFSIZ];
344 FILE *fp;
345 GPtrArray *file_tags, *new_tags;
346 TMTag *tag;
347 TMFileFormat format = TM_FILE_FORMAT_TAGMANAGER;
349 if (NULL == (fp = g_fopen(tags_file, "r")))
350 return FALSE;
351 if ((NULL == fgets((gchar*) buf, BUFSIZ, fp)) || ('\0' == *buf))
353 fclose(fp);
354 return FALSE; /* early out on error */
356 else
357 { /* We read the first line for the format specification. */
358 if (buf[0] == '#' && strstr((gchar*) buf, "format=pipe") != NULL)
359 format = TM_FILE_FORMAT_PIPE;
360 else if (buf[0] == '#' && strstr((gchar*) buf, "format=tagmanager") != NULL)
361 format = TM_FILE_FORMAT_TAGMANAGER;
362 else if (buf[0] == '#' && strstr((gchar*) buf, "format=ctags") != NULL)
363 format = TM_FILE_FORMAT_CTAGS;
364 else if (strncmp((gchar*) buf, "!_TAG_", 6) == 0)
365 format = TM_FILE_FORMAT_CTAGS;
366 else
367 { /* We didn't find a valid format specification, so we try to auto-detect the format
368 * by counting the pipe characters on the first line and asumme pipe format when
369 * we find more than one pipe on the line. */
370 guint i, pipe_cnt = 0, tab_cnt = 0;
371 for (i = 0; i < BUFSIZ && buf[i] != '\0' && pipe_cnt < 2; i++)
373 if (buf[i] == '|')
374 pipe_cnt++;
375 else if (buf[i] == '\t')
376 tab_cnt++;
378 if (pipe_cnt > 1)
379 format = TM_FILE_FORMAT_PIPE;
380 else if (tab_cnt > 1)
381 format = TM_FILE_FORMAT_CTAGS;
383 rewind(fp); /* reset the file pointer, to start reading again from the beginning */
386 file_tags = g_ptr_array_new();
387 while (NULL != (tag = tm_tag_new_from_file(NULL, fp, mode, format)))
388 g_ptr_array_add(file_tags, tag);
389 fclose(fp);
391 tm_tags_sort(file_tags, global_tags_sort_attrs, TRUE, TRUE);
393 /* reorder the whole array, because tm_tags_find expects a sorted array */
394 new_tags = tm_tags_merge(theWorkspace->global_tags,
395 file_tags, global_tags_sort_attrs, TRUE);
396 g_ptr_array_free(theWorkspace->global_tags, TRUE);
397 g_ptr_array_free(file_tags, TRUE);
398 theWorkspace->global_tags = new_tags;
400 g_ptr_array_free(theWorkspace->global_typename_array, TRUE);
401 theWorkspace->global_typename_array = tm_tags_extract(new_tags, TM_GLOBAL_TYPE_MASK);
403 return TRUE;
407 static guint tm_file_inode_hash(gconstpointer key)
409 GStatBuf file_stat;
410 const char *filename = (const char*)key;
411 if (g_stat(filename, &file_stat) == 0)
413 #ifdef TM_DEBUG
414 g_message ("Hash for '%s' is '%d'\n", filename, file_stat.st_ino);
415 #endif
416 return g_direct_hash ((gpointer)(intptr_t)file_stat.st_ino);
417 } else {
418 return 0;
423 static void tm_move_entries_to_g_list(gpointer key, gpointer value, gpointer user_data)
425 GList **pp_list = (GList**)user_data;
427 if (user_data == NULL)
428 return;
430 *pp_list = g_list_prepend(*pp_list, value);
434 static void write_includes_file(FILE *fp, GList *includes_files)
436 GList *node;
438 node = includes_files;
439 while (node)
441 char *str = g_strdup_printf("#include \"%s\"\n", (char*)node->data);
442 size_t str_len = strlen(str);
444 fwrite(str, str_len, 1, fp);
445 g_free(str);
446 node = g_list_next(node);
451 static void append_to_temp_file(FILE *fp, GList *file_list)
453 GList *node;
455 node = file_list;
456 while (node)
458 const char *fname = node->data;
459 char *contents;
460 size_t length;
461 GError *err = NULL;
463 if (! g_file_get_contents(fname, &contents, &length, &err))
465 fprintf(stderr, "Unable to read file: %s\n", err->message);
466 g_error_free(err);
468 else
470 fwrite(contents, length, 1, fp);
471 fwrite("\n", 1, 1, fp); /* in case file doesn't end in newline (e.g. windows). */
472 g_free(contents);
474 node = g_list_next (node);
479 static gchar *create_temp_file(const gchar *tpl)
481 gchar *name;
482 gint fd;
484 fd = g_file_open_tmp(tpl, &name, NULL);
485 if (fd < 0)
486 name = NULL;
487 else
488 close(fd);
490 return name;
494 /* Creates a list of global tags. Ideally, this should be created once during
495 installations so that all users can use the same file. This is because a full
496 scale global tag list can occupy several megabytes of disk space.
497 @param pre_process The pre-processing command. This is executed via system(),
498 so you can pass stuff like 'gcc -E -dD -P `gnome-config --cflags gnome`'.
499 @param includes Include files to process. Wildcards such as '/usr/include/a*.h'
500 are allowed.
501 @param tags_file The file where the tags will be stored.
502 @param lang The language to use for the tags file.
503 @return TRUE on success, FALSE on failure.
505 gboolean tm_workspace_create_global_tags(const char *pre_process, const char **includes,
506 int includes_count, const char *tags_file, int lang)
508 #ifdef HAVE_GLOB_H
509 glob_t globbuf;
510 size_t idx_glob;
511 #endif
512 int idx_inc;
513 char *command;
514 guint i;
515 FILE *fp;
516 TMSourceFile *source_file;
517 GPtrArray *tags_array;
518 GHashTable *includes_files_hash;
519 GList *includes_files = NULL;
520 gchar *temp_file = create_temp_file("tmp_XXXXXX.cpp");
521 gchar *temp_file2 = create_temp_file("tmp_XXXXXX.cpp");
523 if (NULL == temp_file || NULL == temp_file2 ||
524 NULL == (fp = g_fopen(temp_file, "w")))
526 g_free(temp_file);
527 g_free(temp_file2);
528 return FALSE;
531 includes_files_hash = g_hash_table_new_full (tm_file_inode_hash,
532 g_direct_equal,
533 NULL, g_free);
535 #ifdef HAVE_GLOB_H
536 globbuf.gl_offs = 0;
538 if (includes[0][0] == '"') /* leading \" char for glob matching */
539 for(idx_inc = 0; idx_inc < includes_count; idx_inc++)
541 size_t dirty_len = strlen(includes[idx_inc]);
542 char *clean_path = g_malloc(dirty_len - 1);
544 strncpy(clean_path, includes[idx_inc] + 1, dirty_len - 1);
545 clean_path[dirty_len - 2] = 0;
547 #ifdef TM_DEBUG
548 g_message ("[o][%s]\n", clean_path);
549 #endif
550 glob(clean_path, 0, NULL, &globbuf);
552 #ifdef TM_DEBUG
553 g_message ("matches: %d\n", globbuf.gl_pathc);
554 #endif
556 for(idx_glob = 0; idx_glob < globbuf.gl_pathc; idx_glob++)
558 #ifdef TM_DEBUG
559 g_message (">>> %s\n", globbuf.gl_pathv[idx_glob]);
560 #endif
561 if (!g_hash_table_lookup(includes_files_hash,
562 globbuf.gl_pathv[idx_glob]))
564 char* file_name_copy = strdup(globbuf.gl_pathv[idx_glob]);
565 g_hash_table_insert(includes_files_hash, file_name_copy,
566 file_name_copy);
567 #ifdef TM_DEBUG
568 g_message ("Added ...\n");
569 #endif
572 globfree(&globbuf);
573 g_free(clean_path);
575 else
576 #endif
577 /* no glob support or globbing not wanted */
578 for(idx_inc = 0; idx_inc < includes_count; idx_inc++)
580 if (!g_hash_table_lookup(includes_files_hash,
581 includes[idx_inc]))
583 char* file_name_copy = strdup(includes[idx_inc]);
584 g_hash_table_insert(includes_files_hash, file_name_copy,
585 file_name_copy);
589 /* Checks for duplicate file entries which would case trouble */
590 g_hash_table_foreach(includes_files_hash, tm_move_entries_to_g_list,
591 &includes_files);
593 includes_files = g_list_reverse (includes_files);
595 #ifdef TM_DEBUG
596 g_message ("writing out files to %s\n", temp_file);
597 #endif
598 if (pre_process != NULL)
599 write_includes_file(fp, includes_files);
600 else
601 append_to_temp_file(fp, includes_files);
603 g_list_free (includes_files);
604 g_hash_table_destroy(includes_files_hash);
605 includes_files_hash = NULL;
606 includes_files = NULL;
607 fclose(fp);
609 if (pre_process != NULL)
611 gint ret;
612 gchar *tmp_errfile = create_temp_file("tmp_XXXXXX");
613 gchar *errors = NULL;
614 command = g_strdup_printf("%s %s >%s 2>%s",
615 pre_process, temp_file, temp_file2, tmp_errfile);
616 #ifdef TM_DEBUG
617 g_message("Executing: %s", command);
618 #endif
619 ret = system(command);
620 g_free(command);
621 g_unlink(temp_file);
622 g_free(temp_file);
623 g_file_get_contents(tmp_errfile, &errors, NULL, NULL);
624 if (errors && *errors)
625 g_printerr("%s", errors);
626 g_free(errors);
627 g_unlink(tmp_errfile);
628 g_free(tmp_errfile);
629 if (ret == -1)
631 g_unlink(temp_file2);
632 return FALSE;
635 else
637 /* no pre-processing needed, so temp_file2 = temp_file */
638 g_unlink(temp_file2);
639 g_free(temp_file2);
640 temp_file2 = temp_file;
641 temp_file = NULL;
643 source_file = tm_source_file_new(temp_file2, tm_source_file_get_lang_name(lang));
644 update_source_file(source_file, NULL, 0, FALSE, FALSE);
645 if (NULL == source_file)
647 g_unlink(temp_file2);
648 return FALSE;
650 g_unlink(temp_file2);
651 g_free(temp_file2);
652 if (0 == source_file->tags_array->len)
654 tm_source_file_free(source_file);
655 return FALSE;
657 tags_array = tm_tags_extract(source_file->tags_array, tm_tag_max_t);
658 if ((NULL == tags_array) || (0 == tags_array->len))
660 if (tags_array)
661 g_ptr_array_free(tags_array, TRUE);
662 tm_source_file_free(source_file);
663 return FALSE;
665 if (FALSE == tm_tags_sort(tags_array, global_tags_sort_attrs, TRUE, FALSE))
667 tm_source_file_free(source_file);
668 return FALSE;
670 if (NULL == (fp = g_fopen(tags_file, "w")))
672 tm_source_file_free(source_file);
673 return FALSE;
675 fprintf(fp, "# format=tagmanager\n");
676 for (i = 0; i < tags_array->len; ++i)
678 tm_tag_write(TM_TAG(tags_array->pdata[i]), fp, tm_tag_attr_type_t
679 | tm_tag_attr_scope_t | tm_tag_attr_arglist_t | tm_tag_attr_vartype_t
680 | tm_tag_attr_pointer_t);
682 fclose(fp);
683 tm_source_file_free(source_file);
684 g_ptr_array_free(tags_array, TRUE);
685 return TRUE;
689 static gboolean langs_compatible(langType lang, langType other)
691 if (lang == other || lang == -1 || other == -1)
692 return TRUE;
693 /* Accept CPP tags for C lang and vice versa */
694 else if (lang == TM_PARSER_C && other == TM_PARSER_CPP)
695 return TRUE;
696 else if (lang == TM_PARSER_CPP && other == TM_PARSER_C)
697 return TRUE;
699 return FALSE;
703 static void fill_find_tags_array(GPtrArray *dst, const GPtrArray *src,
704 const char *name, const char *scope, TMTagType type, langType lang)
706 TMTag **tag;
707 guint i, num;
709 if (!src || !dst || !name || !*name)
710 return;
712 tag = tm_tags_find(src, name, FALSE, &num);
713 for (i = 0; i < num; ++i)
715 if ((type & (*tag)->type) &&
716 langs_compatible(lang, (*tag)->lang) &&
717 (!scope || g_strcmp0((*tag)->scope, scope) == 0))
719 g_ptr_array_add(dst, *tag);
721 tag++;
726 /* Returns all matching tags found in the workspace.
727 @param name The name of the tag to find.
728 @param scope The scope name of the tag to find, or NULL.
729 @param type The tag types to return (TMTagType). Can be a bitmask.
730 @param attrs The attributes to sort and dedup on (0 terminated integer array).
731 @param lang Specifies the language(see the table in parsers.h) of the tags to be found,
732 -1 for all
733 @return Array of matching tags.
735 GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type,
736 TMTagAttrType *attrs, langType lang)
738 GPtrArray *tags = g_ptr_array_new();
740 fill_find_tags_array(tags, theWorkspace->tags_array, name, scope, type, lang);
741 fill_find_tags_array(tags, theWorkspace->global_tags, name, scope, type, lang);
743 if (attrs)
744 tm_tags_sort(tags, attrs, TRUE, FALSE);
746 return tags;
750 static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src,
751 const char *name, langType lang, guint max_num)
753 TMTag **tag, *last = NULL;
754 guint i, count, num;
756 if (!src || !dst || !name || !*name)
757 return;
759 num = 0;
760 tag = tm_tags_find(src, name, TRUE, &count);
761 for (i = 0; i < count && num < max_num; ++i)
763 if (langs_compatible(lang, (*tag)->lang) &&
764 !tm_tag_is_anon(*tag) &&
765 (!last || g_strcmp0(last->name, (*tag)->name) != 0))
767 g_ptr_array_add(dst, *tag);
768 last = *tag;
769 num++;
771 tag++;
776 /* Returns tags with the specified prefix sorted by name. If there are several
777 tags with the same name, only one of them appears in the resulting array.
778 @param prefix The prefix of the tag to find.
779 @param lang Specifies the language(see the table in parsers.h) of the tags to be found,
780 -1 for all.
781 @param max_num The maximum number of tags to return.
782 @return Array of matching tags sorted by their name.
784 GPtrArray *tm_workspace_find_prefix(const char *prefix, langType lang, guint max_num)
786 TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 };
787 GPtrArray *tags = g_ptr_array_new();
789 fill_find_tags_array_prefix(tags, theWorkspace->tags_array, prefix, lang, max_num);
790 fill_find_tags_array_prefix(tags, theWorkspace->global_tags, prefix, lang, max_num);
792 tm_tags_sort(tags, attrs, TRUE, FALSE);
793 if (tags->len > max_num)
794 tags->len = max_num;
796 return tags;
800 /* Gets all members of type_tag; search them inside the all array.
801 * The namespace parameter determines whether we are performing the "namespace"
802 * search (user has typed something like "A::" where A is a type) or "scope" search
803 * (user has typed "a." where a is a global struct-like variable). With the
804 * namespace search we return all direct descendants of any type while with the
805 * scope search we return only those which can be invoked on a variable (member,
806 * method, etc.). */
807 static GPtrArray *
808 find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespace)
810 TMTagType member_types = tm_tag_max_t & ~(TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t);
811 GPtrArray *tags = g_ptr_array_new();
812 gchar *scope;
813 guint i;
815 if (namespace)
816 member_types = tm_tag_max_t;
818 if (type_tag->scope && *(type_tag->scope))
819 scope = g_strconcat(type_tag->scope, tm_tag_context_separator(type_tag->lang), type_tag->name, NULL);
820 else
821 scope = g_strdup(type_tag->name);
823 for (i = 0; i < all->len; ++i)
825 TMTag *tag = TM_TAG (all->pdata[i]);
827 if (tag && (tag->type & member_types) &&
828 tag->scope && tag->scope[0] != '\0' &&
829 langs_compatible(tag->lang, type_tag->lang) &&
830 strcmp(scope, tag->scope) == 0 &&
831 (!namespace || !tm_tag_is_anon(tag)))
833 g_ptr_array_add (tags, tag);
837 g_free(scope);
839 if (tags->len == 0)
841 g_ptr_array_free(tags, TRUE);
842 return NULL;
845 return tags;
849 static gchar *strip_type(const gchar *scoped_name, langType lang)
851 if (scoped_name != NULL)
853 /* remove scope prefix */
854 const gchar *sep = tm_tag_context_separator(lang);
855 const gchar *base = g_strrstr(scoped_name, sep);
856 gchar *name = base ? g_strdup(base + strlen(sep)) : g_strdup(scoped_name);
858 /* remove pointers */
859 g_strdelimit(name, "*^", ' ');
860 g_strstrip(name);
862 return name;
864 return NULL;
868 /* Gets all members of the type with the given name; search them inside tags_array */
869 static GPtrArray *
870 find_scope_members (const GPtrArray *tags_array, const gchar *name, TMSourceFile *file,
871 langType lang, gboolean namespace)
873 GPtrArray *res = NULL;
874 gchar *type_name;
875 guint i;
877 g_return_val_if_fail(name && *name, NULL);
879 type_name = g_strdup(name);
881 /* Check if type_name is a type that can possibly contain members.
882 * Try to resolve intermediate typedefs to get the real type name. Also
883 * add scope information to the name if applicable.
884 * The loop below loops only when resolving typedefs - avoid possibly infinite
885 * loop when typedefs create a cycle by adding some limits. */
886 for (i = 0; i < 5; i++)
888 guint j;
889 GPtrArray *type_tags;
890 TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t;
891 TMTag *tag = NULL;
893 if (!namespace)
894 types &= ~tm_tag_enum_t;
896 type_tags = g_ptr_array_new();
897 fill_find_tags_array(type_tags, tags_array, type_name, NULL, types, lang);
899 for (j = 0; j < type_tags->len; j++)
901 TMTag *test_tag = TM_TAG(type_tags->pdata[j]);
903 /* anonymous type defined in a different file than the variable -
904 * this isn't the type we are looking for */
905 if (tm_tag_is_anon(test_tag) && (file != test_tag->file || test_tag->file == NULL))
906 continue;
908 tag = test_tag;
910 /* prefer non-typedef tags because we can be sure they contain members */
911 if (test_tag->type != tm_tag_typedef_t)
912 break;
915 g_ptr_array_free(type_tags, TRUE);
917 if (!tag) /* not a type that can contain members */
918 break;
920 /* intermediate typedef - resolve to the real type */
921 if (tag->type == tm_tag_typedef_t)
923 if (tag->var_type && tag->var_type[0] != '\0')
925 g_free(type_name);
926 type_name = strip_type(tag->var_type, tag->lang);
927 file = tag->file;
928 continue;
930 break;
932 else /* real type with members */
934 /* use the same file as the composite type if file information available */
935 res = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace);
936 break;
940 g_free(type_name);
942 return res;
946 /* Checks whether a member tag is directly accessible from method with method_scope */
947 static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *method_scope, TMTag *member_tag,
948 langType lang)
950 const gchar *sep = tm_tag_context_separator(lang);
951 gboolean ret = FALSE;
952 gchar **comps;
953 guint len;
955 /* method scope is in the form ...::class_name::method_name */
956 comps = g_strsplit (method_scope, sep, 0);
957 len = g_strv_length(comps);
958 if (len > 1)
960 gchar *method, *member_scope, *cls, *cls_scope;
962 /* get method/member scope */
963 method = comps[len - 1];
964 comps[len - 1] = NULL;
965 member_scope = g_strjoinv(sep, comps);
966 comps[len - 1] = method;
968 /* get class scope */
969 cls = comps[len - 2];
970 comps[len - 2] = NULL;
971 cls_scope = g_strjoinv(sep, comps);
972 comps[len - 2] = cls;
973 cls_scope = strlen(cls_scope) > 0 ? cls_scope : NULL;
975 /* check whether member inside the class */
976 if (g_strcmp0(member_tag->scope, member_scope) == 0)
978 const GPtrArray *src = member_tag->file ? member_tag->file->tags_array : tags;
979 GPtrArray *cls_tags = g_ptr_array_new();
981 /* check whether the class exists */
982 fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS | tm_tag_namespace_t, lang);
983 ret = cls_tags->len > 0;
984 g_ptr_array_free(cls_tags, TRUE);
987 g_free(cls_scope);
988 g_free(member_scope);
991 g_strfreev(comps);
992 return ret;
996 /* For an array of variable/type tags, find members inside the types */
997 static GPtrArray *
998 find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, langType lang,
999 gboolean member, const gchar *current_scope)
1001 GPtrArray *member_tags = NULL;
1002 guint i;
1004 /* there may be several variables/types with the same name - try each of them until
1005 * we find something */
1006 for (i = 0; i < tags->len && !member_tags; i++)
1008 TMTag *tag = TM_TAG(tags->pdata[i]);
1009 TMTagType member_types = tm_tag_member_t | tm_tag_field_t | tm_tag_method_t;
1010 TMTagType types = TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t;
1012 if (tag->type & types) /* type: namespace search */
1014 if (tag->type & tm_tag_typedef_t)
1015 member_tags = find_scope_members(searched_array, tag->name, tag->file, lang, TRUE);
1016 else
1017 member_tags = find_scope_members_tags(tag->file ? tag->file->tags_array : searched_array,
1018 tag, TRUE);
1020 else if (tag->var_type) /* variable: scope search */
1022 /* The question now is whether we should use member tags (such as
1023 * tm_tag_field_t, tm_tag_member_t) or not. We want them if member==TRUE
1024 * (which means user has typed something like foo.bar.) or if we are
1025 * inside a method where foo is a class member, we want scope completion
1026 * for foo. */
1027 if (!(tag->type & member_types) || member ||
1028 member_at_method_scope(tags, current_scope, tag, lang))
1030 gchar *tag_type = strip_type(tag->var_type, tag->lang);
1032 member_tags = find_scope_members(searched_array, tag_type, tag->file, lang, FALSE);
1033 g_free(tag_type);
1038 return member_tags;
1042 /* Returns all member tags of a struct/union/class if the provided name is a variable
1043 of such a type or the name of the type.
1044 @param source_file TMSourceFile of the edited source file or NULL if not available
1045 @param name Name of the variable/type whose members are searched
1046 @param function TRUE if the name is a name of a function
1047 @param member TRUE if invoked on class/struct member (e.g. after the last dot in foo.bar.)
1048 @param current_scope The current scope in the editor
1049 @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */
1050 GPtrArray *
1051 tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name,
1052 gboolean function, gboolean member, const gchar *current_scope)
1054 langType lang = source_file ? source_file->lang : -1;
1055 GPtrArray *tags, *member_tags = NULL;
1056 TMTagType function_types = tm_tag_function_t | tm_tag_method_t |
1057 tm_tag_macro_with_arg_t | tm_tag_prototype_t;
1058 TMTagType tag_type = tm_tag_max_t &
1059 ~(function_types | tm_tag_enumerator_t | tm_tag_namespace_t | tm_tag_package_t);
1060 TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, 0};
1062 if (function)
1063 tag_type = function_types;
1065 /* tags corresponding to the variable/type name */
1066 tags = tm_workspace_find(name, NULL, tag_type, NULL, lang);
1068 /* Start searching inside the source file, continue with workspace tags and
1069 * end with global tags. This way we find the "closest" tag to the current
1070 * file in case there are more of them. */
1071 if (source_file)
1072 member_tags = find_scope_members_all(tags, source_file->tags_array,
1073 lang, member, current_scope);
1074 if (!member_tags)
1075 member_tags = find_scope_members_all(tags, theWorkspace->tags_array, lang,
1076 member, current_scope);
1077 if (!member_tags)
1078 member_tags = find_scope_members_all(tags, theWorkspace->global_tags, lang,
1079 member, current_scope);
1081 g_ptr_array_free(tags, TRUE);
1083 tm_tags_dedup(member_tags, sort_attr, FALSE);
1085 return member_tags;
1089 #ifdef TM_DEBUG
1091 /* Dumps the workspace tree - useful for debugging */
1092 void tm_workspace_dump(void)
1094 guint i;
1096 #ifdef TM_DEBUG
1097 g_message("Dumping TagManager workspace tree..");
1098 #endif
1099 for (i=0; i < theWorkspace->source_files->len; ++i)
1101 TMSourceFile *source_file = theWorkspace->source_files->pdata[i];
1102 fprintf(stderr, "%s", source_file->file_name);
1105 #endif /* TM_DEBUG */
1108 #if 0
1110 /* Returns a list of parent classes for the given class name
1111 @param name Name of the class
1112 @return A GPtrArray of TMTag pointers (includes the TMTag for the class) */
1113 static const GPtrArray *tm_workspace_get_parents(const gchar *name)
1115 static TMTagAttrType type[] = { tm_tag_attr_name_t, tm_tag_attr_none_t };
1116 static GPtrArray *parents = NULL;
1117 const GPtrArray *matches;
1118 guint i = 0;
1119 guint j;
1120 gchar **klasses;
1121 gchar **klass;
1122 TMTag *tag;
1124 g_return_val_if_fail(name && isalpha(*name),NULL);
1126 if (NULL == parents)
1127 parents = g_ptr_array_new();
1128 else
1129 g_ptr_array_set_size(parents, 0);
1130 matches = tm_workspace_find(name, NULL, tm_tag_class_t, type, -1);
1131 if ((NULL == matches) || (0 == matches->len))
1132 return NULL;
1133 g_ptr_array_add(parents, matches->pdata[0]);
1134 while (i < parents->len)
1136 tag = TM_TAG(parents->pdata[i]);
1137 if ((NULL != tag->inheritance) && (isalpha(tag->inheritance[0])))
1139 klasses = g_strsplit(tag->inheritance, ",", 10);
1140 for (klass = klasses; (NULL != *klass); ++ klass)
1142 for (j=0; j < parents->len; ++j)
1144 if (0 == strcmp(*klass, TM_TAG(parents->pdata[j])->name))
1145 break;
1147 if (parents->len == j)
1149 matches = tm_workspace_find(*klass, NULL, tm_tag_class_t, type, -1);
1150 if ((NULL != matches) && (0 < matches->len))
1151 g_ptr_array_add(parents, matches->pdata[0]);
1154 g_strfreev(klasses);
1156 ++ i;
1158 return parents;
1161 #endif