Autocomplete scoped fields like struct members when typing '.' (and

also '->' or '::' in C/C++).
Save all tag types for C/C++ when generating a global tags file, so
we can use autocompletion for structs also.
Merge tm_workspace_find_scope_members(),
tm_workspace_find_namespace_members() (currently not built) from
Anjuta 2.24.1 tagmanager.



git-svn-id: https://geany.svn.sourceforge.net/svnroot/geany/trunk@3850 ea778897-0a13-0410-b9d1-a72fbfd435f5
This commit is contained in:
Nick Treleaven 2009-06-10 12:36:13 +00:00
parent dcc89a6f61
commit 380cb1fc6b
5 changed files with 589 additions and 48 deletions

View File

@ -1,3 +1,16 @@
2009-06-10 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
* src/editor.c, tagmanager/include/tm_workspace.h,
tagmanager/tm_workspace.c, TODO:
Autocomplete scoped fields like struct members when typing '.' (and
also '->' or '::' in C/C++).
Save all tag types for C/C++ when generating a global tags file, so
we can use autocompletion for structs also.
Merge tm_workspace_find_scope_members(),
tm_workspace_find_namespace_members() (currently not built) from
Anjuta 2.24.1 tagmanager.
2009-06-09 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
* tagmanager/pascal.c:

1
TODO
View File

@ -41,5 +41,4 @@ Note: these items might not get worked on.
o (better tags support for popular languages? - this is a moving
target...)
o Some kind of support for CTags tags files
o Scope resolution for object members
o Python plugin interface (different concept from Lua scripting)

View File

@ -431,6 +431,103 @@ static void check_line_breaking(GeanyEditor *editor, gint pos, gchar c)
}
static void show_autocomplete(ScintillaObject *sci, gint rootlen, const gchar *words)
{
/* store whether a calltip is showing, so we can reshow it after autocompletion */
calltip.set = SSM(sci, SCI_CALLTIPACTIVE, 0, 0);
SSM(sci, SCI_AUTOCSHOW, rootlen, (sptr_t) words);
}
static void show_tags_list(GeanyEditor *editor, const GPtrArray *tags, gsize rootlen)
{
ScintillaObject *sci = editor->sci;
g_return_if_fail(tags);
if (tags->len > 0)
{
GString *words = g_string_sized_new(150);
guint j;
for (j = 0; j < tags->len; ++j)
{
if (j > 0)
g_string_append_c(words, '\n');
if (j == editor_prefs.autocompletion_max_entries)
{
g_string_append(words, "...");
break;
}
g_string_append(words, ((TMTag *) tags->pdata[j])->name);
}
show_autocomplete(sci, rootlen, words->str);
g_string_free(words, TRUE);
}
}
/* do not use with long strings */
static gboolean match_last_chars(ScintillaObject *sci, gint pos, const gchar *str)
{
gsize len = strlen(str);
gchar *buf;
g_return_val_if_fail(len < 100, FALSE);
buf = g_alloca(len + 1);
sci_get_text_range(sci, pos - len, pos, buf);
return strcmp(str, buf) == 0;
}
static void autocomplete_scope(GeanyEditor *editor)
{
ScintillaObject *sci = editor->sci;
gint pos = sci_get_current_position(editor->sci);
gchar typed = sci_get_char_at(sci, pos - 1);
gchar *name;
const GPtrArray *tags = NULL;
const TMTag *tag;
gint ft_id = FILETYPE_ID(editor->document->file_type);
if (ft_id == GEANY_FILETYPES_C || ft_id == GEANY_FILETYPES_CPP)
{
if (match_last_chars(sci, pos, "->") || match_last_chars(sci, pos, "::"))
pos--;
else if (typed != '.')
return;
}
else if (typed != '.')
return;
/* allow for a space between word and operator */
if (isspace(sci_get_char_at(sci, pos - 2)))
pos--;
name = editor_get_word_at_pos(editor, pos - 1, NULL);
if (!name)
return;
tags = tm_workspace_find(name, tm_tag_max_t, NULL, FALSE, -1);
g_free(name);
if (!tags || tags->len == 0)
return;
tag = g_ptr_array_index(tags, 0);
name = tag->atts.entry.var_type;
if (name)
{
TMWorkObject *obj = editor->document->tm_file;
tags = tm_workspace_find_scope_members(obj ? obj->tags_array : NULL,
name, TRUE, FALSE);
if (tags)
show_tags_list(editor, tags, 0);
}
}
static void on_char_added(GeanyEditor *editor, SCNotification *nt)
{
ScintillaObject *sci = editor->sci;
@ -450,6 +547,8 @@ static void on_char_added(GeanyEditor *editor, SCNotification *nt)
break;
}
case '>':
editor_start_auto_complete(editor, pos, FALSE); /* C/C++ ptr-> scope completion */
/* fall through */
case '/':
{ /* close xml-tags */
handle_xml(editor, pos, nt->ch);
@ -489,6 +588,10 @@ static void on_char_added(GeanyEditor *editor, SCNotification *nt)
close_block(editor, pos - 1);
break;
}
/* scope autocompletion */
case '.':
case ':': /* C/C++ class:: syntax */
/* tag autocompletion */
default:
editor_start_auto_complete(editor, pos, FALSE);
}
@ -1520,14 +1623,6 @@ gchar *editor_get_calltip_text(GeanyEditor *editor, const TMTag *tag)
}
static void show_autocomplete(ScintillaObject *sci, gint rootlen, const gchar *words)
{
/* store whether a calltip is showing, so we can reshow it after autocompletion */
calltip.set = SSM(sci, SCI_CALLTIPACTIVE, 0, 0);
SSM(sci, SCI_AUTOCSHOW, rootlen, (sptr_t) words);
}
static gboolean
autocomplete_html(ScintillaObject *sci, const gchar *root, gsize rootlen)
{ /* HTML entities auto completion */
@ -1566,35 +1661,15 @@ autocomplete_tags(GeanyEditor *editor, const gchar *root, gsize rootlen)
{
TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 };
const GPtrArray *tags;
ScintillaObject *sci;
GeanyDocument *doc;
g_return_val_if_fail(editor != NULL && editor->document->file_type != NULL, FALSE);
sci = editor->sci;
doc = editor->document;
tags = tm_workspace_find(root, tm_tag_max_t, attrs, TRUE, doc->file_type->lang);
if (NULL != tags && tags->len > 0)
{
GString *words = g_string_sized_new(150);
guint j;
for (j = 0; j < tags->len; ++j)
{
if (j > 0)
g_string_append_c(words, '\n');
if (j == editor_prefs.autocompletion_max_entries)
{
g_string_append(words, "...");
break;
}
g_string_append(words, ((TMTag *) tags->pdata[j])->name);
}
show_autocomplete(sci, rootlen, words->str);
g_string_free(words, TRUE);
}
if (tags)
show_tags_list(editor, tags, rootlen);
return TRUE;
}
@ -1658,6 +1733,8 @@ gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean forc
if (!force && !is_code_style(lexer, style))
return FALSE;
autocomplete_scope(editor);
linebuf = sci_get_line(sci, line);
if (ft->id == GEANY_FILETYPES_LATEX)

View File

@ -139,6 +139,19 @@ const GPtrArray *
tm_workspace_find_scoped (const char *name, const char *scope, gint type,
TMTagAttrType *attrs, gboolean partial, langType lang, gboolean global_search);
/*! Returns all matching members tags found in given struct/union/class name.
\param name Name of the struct/union/class.
\param file_tags A GPtrArray of edited file TMTag pointers (for search speedup, can be NULL).
\return A GPtrArray of TMTag pointers to struct/union/class members */
const GPtrArray *tm_workspace_find_scope_members(const GPtrArray *file_tags,
const char *scope_name,
gboolean find_global,
gboolean no_definitions);
const GPtrArray *
tm_workspace_find_namespace_members (const GPtrArray * file_tags, const char *name,
gboolean search_global);
/* Returns TMTag to function which "own" given line
\param line Current line in edited file.
\param file_tags A GPtrArray of edited file TMTag pointers.

View File

@ -257,22 +257,6 @@ static void append_to_temp_file(FILE *fp, GList *file_list)
}
}
static gint get_global_tag_type_mask(gint lang)
{
switch (lang)
{
case 0:
case 1:
/* C/C++ */
return tm_tag_class_t | tm_tag_typedef_t | tm_tag_enum_t | tm_tag_enumerator_t |
tm_tag_prototype_t |
tm_tag_function_t | tm_tag_method_t | /* for inline functions */
tm_tag_macro_t | tm_tag_macro_with_arg_t;
default:
return tm_tag_max_t;
}
}
gboolean tm_workspace_create_global_tags(const char *config_dir, const char *pre_process,
const char **includes, int includes_count, const char *tags_file, int lang)
{
@ -415,7 +399,7 @@ gboolean tm_workspace_create_global_tags(const char *config_dir, const char *pre
tm_source_file_free(source_file);
return FALSE;
}
tags_array = tm_tags_extract(source_file->tags_array, get_global_tag_type_mask(lang));
tags_array = tm_tags_extract(source_file->tags_array, tm_tag_max_t);
if ((NULL == tags_array) || (0 == tags_array->len))
{
if (tags_array)
@ -752,6 +736,461 @@ tm_get_current_function (GPtrArray * file_tags, const gulong line)
}
static int
find_scope_members_tags (const GPtrArray * all, GPtrArray * tags,
const langType langJava, const char *name,
const char *filename, gboolean no_definitions)
{
GPtrArray *local = g_ptr_array_new ();
unsigned int i;
TMTag *tag;
size_t len = strlen (name);
for (i = 0; (i < all->len); ++i)
{
tag = TM_TAG (all->pdata[i]);
if (no_definitions && filename && tag->atts.entry.file &&
0 != strcmp (filename,
tag->atts.entry.file->work_object.short_name))
{
continue;
}
if (tag && tag->atts.entry.scope && tag->atts.entry.scope[0] != '\0')
{
if (0 == strncmp (name, tag->atts.entry.scope, len))
{
g_ptr_array_add (local, tag);
}
}
}
if (local->len > 0)
{
unsigned int j;
TMTag *tag2;
char backup = 0;
char *s_backup = NULL;
char *var_type = NULL;
char *scope;
for (i = 0; (i < local->len); ++i)
{
tag = TM_TAG (local->pdata[i]);
scope = tag->atts.entry.scope;
if (scope && 0 == strcmp (name, scope))
{
g_ptr_array_add (tags, tag);
continue;
}
s_backup = NULL;
j = 0; /* someone could write better code :P */
while (scope)
{
if (s_backup)
{
backup = s_backup[0];
s_backup[0] = '\0';
if (0 == strcmp (name, tag->atts.entry.scope))
{
j = local->len;
s_backup[0] = backup;
break;
}
}
if (tag->atts.entry.file
&& tag->atts.entry.file->lang == langJava)
{
scope = strrchr (tag->atts.entry.scope, '.');
if (scope)
var_type = scope + 1;
}
else
{
scope = strrchr (tag->atts.entry.scope, ':');
if (scope)
{
var_type = scope + 1;
scope--;
}
}
if (s_backup)
{
s_backup[0] = backup;
}
if (scope)
{
if (s_backup)
{
backup = s_backup[0];
s_backup[0] = '\0';
}
for (j = 0; (j < local->len); ++j)
{
if (i == j)
continue;
tag2 = TM_TAG (local->pdata[j]);
if (tag2->atts.entry.var_type &&
0 == strcmp (var_type, tag2->atts.entry.var_type))
{
break;
}
}
if (s_backup)
s_backup[0] = backup;
}
if (j < local->len)
{
break;
}
s_backup = scope;
}
if (j == local->len)
{
g_ptr_array_add (tags, tag);
}
}
}
g_ptr_array_free (local, TRUE);
return (int) tags->len;
}
#if 0
static int
find_namespace_members_tags (const GPtrArray * all, GPtrArray * tags,
const langType langJava, const char *name,
const char *filename)
{
GPtrArray *local = g_ptr_array_new ();
unsigned int i;
TMTag *tag;
size_t len = strlen (name);
g_return_val_if_fail (all != NULL, 0);
for (i = 0; (i < all->len); ++i)
{
tag = TM_TAG (all->pdata[i]);
if (filename && tag->atts.entry.file &&
0 != strcmp (filename,
tag->atts.entry.file->work_object.short_name))
{
continue;
}
if (tag && tag->atts.entry.scope && tag->atts.entry.scope[0] != '\0')
{
if (0 == strncmp (name, tag->atts.entry.scope, len))
{
g_ptr_array_add (local, tag);
}
}
}
if (local->len > 0)
{
char *scope;
for (i = 0; (i < local->len); ++i)
{
tag = TM_TAG (local->pdata[i]);
scope = tag->atts.entry.scope;
/* if we wanna complete something like
* namespace1::
* we'll just return the tags that have "namespace1"
* as their scope. So we won't return classes/members/namespaces
* under, for example, namespace2, where namespace1::namespace2
*/
if (scope && 0 == strcmp (name, scope))
{
g_ptr_array_add (tags, tag);
}
}
}
g_ptr_array_free (local, TRUE);
return (int) tags->len;
}
const GPtrArray *
tm_workspace_find_namespace_members (const GPtrArray * file_tags, const char *name,
gboolean search_global)
{
static GPtrArray *tags = NULL;
GPtrArray *local = NULL;
char *new_name = (char *) name;
char *filename = NULL;
int found = 0, del = 0;
static langType langJava = -1;
TMTag *tag = NULL;
g_return_val_if_fail ((theWorkspace && name && name[0] != '\0'), NULL);
if (!tags)
tags = g_ptr_array_new ();
while (1)
{
const GPtrArray *tags2;
int got = 0, types = (tm_tag_class_t | tm_tag_namespace_t |
tm_tag_struct_t | tm_tag_typedef_t |
tm_tag_union_t | tm_tag_enum_t);
if (file_tags)
{
g_ptr_array_set_size (tags, 0);
got = fill_find_tags_array (tags, file_tags,
new_name, NULL, types, FALSE, -1, FALSE);
}
if (got)
{
tags2 = tags;
}
else
{
TMTagAttrType attrs[] = {
tm_tag_attr_name_t, tm_tag_attr_type_t,
tm_tag_attr_none_t
};
tags2 = tm_workspace_find (new_name, types, attrs, FALSE, -1);
}
if ((tags2) && (tags2->len == 1) && (tag = TM_TAG (tags2->pdata[0])))
{
if (tag->type == tm_tag_typedef_t && tag->atts.entry.var_type
&& tag->atts.entry.var_type[0] != '\0')
{
new_name = tag->atts.entry.var_type;
continue;
}
filename = (tag->atts.entry.file ?
tag->atts.entry.file->work_object.short_name : NULL);
if (tag->atts.entry.scope && tag->atts.entry.scope[0] != '\0')
{
del = 1;
if (tag->atts.entry.file &&
tag->atts.entry.file->lang == langJava)
{
new_name = g_strdup_printf ("%s.%s",
tag->atts.entry.scope,
new_name);
}
else
{
new_name = g_strdup_printf ("%s::%s",
tag->atts.entry.scope,
new_name);
}
}
break;
}
else
{
return NULL;
}
}
g_ptr_array_set_size (tags, 0);
if (tag && tag->atts.entry.file)
{
local = tm_tags_extract (tag->atts.entry.file->work_object.tags_array,
(tm_tag_function_t |
tm_tag_field_t | tm_tag_enumerator_t |
tm_tag_namespace_t | tm_tag_class_t ));
}
else
{
local = tm_tags_extract (theWorkspace->work_object.tags_array,
(tm_tag_function_t | tm_tag_prototype_t |
tm_tag_member_t |
tm_tag_field_t | tm_tag_enumerator_t |
tm_tag_namespace_t | tm_tag_class_t ));
}
if (local)
{
found = find_namespace_members_tags (local, tags,
langJava, new_name, filename);
g_ptr_array_free (local, TRUE);
}
if (!found && search_global)
{
GPtrArray *global = tm_tags_extract (theWorkspace->global_tags,
(tm_tag_member_t |
tm_tag_prototype_t |
tm_tag_field_t |
tm_tag_method_t |
tm_tag_function_t |
tm_tag_enumerator_t |
tm_tag_namespace_t |
tm_tag_class_t ));
if (global)
{
find_namespace_members_tags (global, tags, langJava,
new_name, filename);
/*/
DEBUG_PRINT ("returning these");
gint i;
for (i=0; i < tags->len; i++) {
TMTag *cur_tag;
cur_tag = (TMTag*)g_ptr_array_index (tags, i);
tm_tag_print (cur_tag, stdout );
}
/*/
g_ptr_array_free (global, TRUE);
}
}
if (del)
{
g_free (new_name);
}
return tags;
}
#endif
const GPtrArray *
tm_workspace_find_scope_members (const GPtrArray * file_tags, const char *name,
gboolean search_global, gboolean no_definitions)
{
static GPtrArray *tags = NULL;
GPtrArray *local = NULL;
char *new_name = (char *) name;
char *filename = NULL;
int found = 0, del = 0;
static langType langJava = -1;
TMTag *tag = NULL;
/* FIXME */
/* langJava = getNamedLanguage ("Java"); */
g_return_val_if_fail ((theWorkspace && name && name[0] != '\0'), NULL);
if (!tags)
tags = g_ptr_array_new ();
while (1)
{
const GPtrArray *tags2;
int got = 0, types = (tm_tag_class_t | tm_tag_namespace_t |
tm_tag_struct_t | tm_tag_typedef_t |
tm_tag_union_t | tm_tag_enum_t);
if (file_tags)
{
g_ptr_array_set_size (tags, 0);
got = fill_find_tags_array (tags, file_tags,
new_name, NULL, types, FALSE, -1, FALSE);
}
if (got)
{
tags2 = tags;
}
else
{
TMTagAttrType attrs[] = {
tm_tag_attr_name_t, tm_tag_attr_type_t,
tm_tag_attr_none_t
};
tags2 = tm_workspace_find (new_name, types, attrs, FALSE, -1);
}
if ((tags2) && (tags2->len == 1) && (tag = TM_TAG (tags2->pdata[0])))
{
if (tag->type == tm_tag_typedef_t && tag->atts.entry.var_type
&& tag->atts.entry.var_type[0] != '\0')
{
char *tmp_name;
tmp_name = tag->atts.entry.var_type;
if (strcmp(tmp_name, new_name) == 0) {
new_name = NULL;
}
else {
new_name = tmp_name;
}
continue;
}
filename = (tag->atts.entry.file ?
tag->atts.entry.file->work_object.short_name : NULL);
if (tag->atts.entry.scope && tag->atts.entry.scope[0] != '\0')
{
del = 1;
if (tag->atts.entry.file &&
tag->atts.entry.file->lang == langJava)
{
new_name = g_strdup_printf ("%s.%s",
tag->atts.entry.scope,
new_name);
}
else
{
new_name = g_strdup_printf ("%s::%s",
tag->atts.entry.scope,
new_name);
}
}
break;
}
else
{
return NULL;
}
}
g_ptr_array_set_size (tags, 0);
if (no_definitions && tag && tag->atts.entry.file)
{
local = tm_tags_extract (tag->atts.entry.file->work_object.tags_array,
(tm_tag_function_t | tm_tag_prototype_t |
tm_tag_member_t | tm_tag_field_t |
tm_tag_method_t | tm_tag_enumerator_t));
}
else
{
local = tm_tags_extract (theWorkspace->work_object.tags_array,
(tm_tag_function_t | tm_tag_prototype_t |
tm_tag_member_t | tm_tag_field_t |
tm_tag_method_t | tm_tag_enumerator_t));
}
if (local)
{
found = find_scope_members_tags (local, tags, langJava, new_name,
filename, no_definitions);
g_ptr_array_free (local, TRUE);
}
if (!found && search_global)
{
GPtrArray *global = tm_tags_extract (theWorkspace->global_tags,
(tm_tag_member_t |
tm_tag_prototype_t |
tm_tag_field_t |
tm_tag_method_t |
tm_tag_function_t |
tm_tag_enumerator_t
|tm_tag_struct_t | tm_tag_typedef_t |
tm_tag_union_t | tm_tag_enum_t));
if (global)
{
find_scope_members_tags (global, tags, langJava, new_name,
filename, no_definitions);
g_ptr_array_free (global, TRUE);
}
}
if (del)
{
g_free (new_name);
}
return tags;
}
const GPtrArray *tm_workspace_get_parents(const gchar *name)
{
static TMTagAttrType type[] = { tm_tag_attr_name_t, tm_tag_attr_none_t };