From 8ff56a5c779ffcccef0ceb0b412be62f606c76e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 17 Apr 2022 21:13:16 +0200 Subject: [PATCH 01/11] Rename tm_tag_file_t to tm_tag_local_var_t tm_tag_file_t is unused in Geany (and we'll probably never need tags for files) so let's use it for local variable tags. --- src/tagmanager/tm_parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tagmanager/tm_parser.h b/src/tagmanager/tm_parser.h index e6ed5fe680..0a44f67c23 100644 --- a/src/tagmanager/tm_parser.h +++ b/src/tagmanager/tm_parser.h @@ -40,7 +40,7 @@ typedef enum tm_tag_externvar_t = 32768, /**< Extern or forward declaration */ tm_tag_macro_t = 65536, /**< Macro (without arguments) */ tm_tag_macro_with_arg_t = 131072, /**< Parameterized macro */ - tm_tag_file_t = 262144, /**< File (Pseudo tag) - obsolete */ + tm_tag_local_var_t = 262144, /**< Local variable (inside function) */ tm_tag_other_t = 524288, /**< Other (non C/C++/Java tag) */ tm_tag_max_t = 1048575 /**< Maximum value of TMTagType */ } TMTagType; From d5cc1d05fb82218d01f1d61a9329c09a43cf0d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 17 Apr 2022 22:46:15 +0200 Subject: [PATCH 02/11] Enable local tag generation for C/C++ Enable generating these tags both for local variables and function parameters - those are more or less identical for what we will be using local tags for so they can be mapped to the same type. Local tags aren't interesting for tag files so filter them out when generating these (but this also means that we cannot create unit tests for them). --- src/symbols.c | 2 +- src/tagmanager/tm_parser.c | 6 +++--- src/tagmanager/tm_workspace.c | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/symbols.c b/src/symbols.c index c70d976049..51a5a93f1f 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -1133,7 +1133,7 @@ gboolean symbols_recreate_tag_list(GeanyDocument *doc, gint sort_mode) g_return_val_if_fail(DOC_VALID(doc), FALSE); - tags = get_tag_list(doc, tm_tag_max_t); + tags = get_tag_list(doc, ~tm_tag_local_var_t); if (tags == NULL) return FALSE; diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index a305674e2d..6e122e1b21 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -70,8 +70,8 @@ static GHashTable *subparser_map = NULL; {'v', tm_tag_variable_t}, /* variable */ \ {'x', tm_tag_externvar_t}, /* externvar */ \ {'h', tm_tag_undef_t}, /* header */ \ - {'l', tm_tag_undef_t}, /* local */ \ - {'z', tm_tag_undef_t}, /* parameter */ \ + {'l', tm_tag_local_var_t}, /* local */ \ + {'z', tm_tag_local_var_t}, /* parameter */ \ {'L', tm_tag_undef_t}, /* label */ \ {'D', tm_tag_undef_t}, /* macroparam */ @@ -89,7 +89,7 @@ static TMParserMapGroup group_C[] = { {_("Structs"), TM_ICON_STRUCT, tm_tag_union_t | tm_tag_struct_t}, {_("Typedefs / Enums"), TM_ICON_STRUCT, tm_tag_typedef_t | tm_tag_enum_t}, {_("Macros"), TM_ICON_MACRO, tm_tag_macro_t | tm_tag_macro_with_arg_t}, - {_("Variables"), TM_ICON_VAR, tm_tag_variable_t | tm_tag_enumerator_t}, + {_("Variables"), TM_ICON_VAR, tm_tag_variable_t | tm_tag_enumerator_t | tm_tag_local_var_t}, {_("Extern Variables"), TM_ICON_VAR, tm_tag_externvar_t}, {_("Other"), TM_ICON_OTHER, tm_tag_other_t}, }; diff --git a/src/tagmanager/tm_workspace.c b/src/tagmanager/tm_workspace.c index 0f34477503..a0ecaa1e1d 100644 --- a/src/tagmanager/tm_workspace.c +++ b/src/tagmanager/tm_workspace.c @@ -580,6 +580,7 @@ gboolean tm_workspace_create_global_tags(const char *pre_process, const char **i TMSourceFile *source_file; GList *includes_files; gchar *temp_file = create_temp_file("tmp_XXXXXX.cpp"); + GPtrArray *filtered_tags; if (!temp_file) return FALSE; @@ -624,7 +625,9 @@ gboolean tm_workspace_create_global_tags(const char *pre_process, const char **i } tm_tags_sort(source_file->tags_array, global_tags_sort_attrs, TRUE, FALSE); - ret = tm_source_file_write_tags_file(tags_file, source_file->tags_array); + filtered_tags = tm_tags_extract(source_file->tags_array, ~tm_tag_local_var_t); + ret = tm_source_file_write_tags_file(tags_file, filtered_tags); + g_ptr_array_free(filtered_tags, TRUE); tm_source_file_free(source_file); cleanup: From 9e4ef229a666b0aae746b964514d8ecdb12c9d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Mon, 18 Apr 2022 22:49:33 +0200 Subject: [PATCH 03/11] Update (non-scope) autocompletion to take into account local variables We have to ignore local variables that: 1. Are from a different file 2. Are declared later in the file than where the current cursor is 3. Have different scope than the current function scope --- src/editor.c | 11 ++++++-- src/tagmanager/tm_workspace.c | 49 +++++++++++++++++++++++++++-------- src/tagmanager/tm_workspace.h | 4 ++- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/editor.c b/src/editor.c index dd2fd84c2f..986f5225f6 100644 --- a/src/editor.c +++ b/src/editor.c @@ -2019,12 +2019,19 @@ gboolean editor_show_calltip(GeanyEditor *editor, gint pos) static gboolean autocomplete_tags(GeanyEditor *editor, GeanyFiletype *ft, const gchar *root, gsize rootlen) { + GeanyDocument *doc = editor->document; + const gchar *current_scope = NULL; + guint current_line; GPtrArray *tags; gboolean found; - g_return_val_if_fail(editor, FALSE); + g_return_val_if_fail(editor && doc, FALSE); + + symbols_get_current_function(doc, ¤t_scope); + current_line = sci_get_current_line(editor->sci) + 1; - tags = tm_workspace_find_prefix(root, ft->lang, editor_prefs.autocompletion_max_entries); + tags = tm_workspace_find_prefix(root, doc->tm_file, current_line, current_scope, + ft->lang, editor_prefs.autocompletion_max_entries); found = tags->len > 0; if (found) show_tags_list(editor, tags, rootlen); diff --git a/src/tagmanager/tm_workspace.c b/src/tagmanager/tm_workspace.c index a0ecaa1e1d..d110e0499b 100644 --- a/src/tagmanager/tm_workspace.c +++ b/src/tagmanager/tm_workspace.c @@ -684,8 +684,25 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type } +static gboolean is_valid_autocomplete_tag(TMTag *tag, + TMSourceFile *current_file, + guint current_line, + const gchar *current_scope) +{ + /* ignore local variables from other files/functions or after current line */ + return !(tag->type & tm_tag_local_var_t) || + (current_file == tag->file && + current_line >= tag->line && + g_strcmp0(current_scope, tag->scope) == 0); +} + + static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src, - const char *name, TMParserType lang, guint max_num) + const char *name, + TMSourceFile *current_file, + guint current_line, + const gchar *current_scope, + TMParserType lang, guint max_num) { TMTag **tag, *last = NULL; guint i, count, num; @@ -697,20 +714,24 @@ static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src, tag = tm_tags_find(src, name, TRUE, &count); for (i = 0; i < count && num < max_num; ++i) { - if (tm_parser_langs_compatible(lang, (*tag)->lang) && - !tm_tag_is_anon(*tag) && - (!last || g_strcmp0(last->name, (*tag)->name) != 0)) + if (is_valid_autocomplete_tag(*tag, current_file, current_line, current_scope)) { - g_ptr_array_add(dst, *tag); - last = *tag; - num++; + if (tm_parser_langs_compatible(lang, (*tag)->lang) && + !tm_tag_is_anon(*tag) && + (!last || g_strcmp0(last->name, (*tag)->name) != 0)) + { + g_ptr_array_add(dst, *tag); + last = *tag; + num++; + } } tag++; } } -/* Returns tags with the specified prefix sorted by name. If there are several +/* Returns tags with the specified prefix sorted by name, ignoring local + variables from other files/functions or after current line. If there are several tags with the same name, only one of them appears in the resulting array. @param prefix The prefix of the tag to find. @param lang Specifies the language(see the table in tm_parsers.h) of the tags to be found, @@ -718,13 +739,19 @@ static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src, @param max_num The maximum number of tags to return. @return Array of matching tags sorted by their name. */ -GPtrArray *tm_workspace_find_prefix(const char *prefix, TMParserType lang, guint max_num) +GPtrArray *tm_workspace_find_prefix(const char *prefix, + TMSourceFile *current_file, + guint current_line, + const gchar *current_scope, + TMParserType lang, guint max_num) { TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 }; GPtrArray *tags = g_ptr_array_new(); - fill_find_tags_array_prefix(tags, theWorkspace->tags_array, prefix, lang, max_num); - fill_find_tags_array_prefix(tags, theWorkspace->global_tags, prefix, lang, max_num); + fill_find_tags_array_prefix(tags, theWorkspace->tags_array, prefix, + current_file, current_line, current_scope, lang, max_num); + fill_find_tags_array_prefix(tags, theWorkspace->global_tags, prefix, + current_file, current_line, current_scope, lang, max_num); tm_tags_sort(tags, attrs, TRUE, FALSE); if (tags->len > max_num) diff --git a/src/tagmanager/tm_workspace.h b/src/tagmanager/tm_workspace.h index df586911eb..ccc78bfc01 100644 --- a/src/tagmanager/tm_workspace.h +++ b/src/tagmanager/tm_workspace.h @@ -56,7 +56,9 @@ gboolean tm_workspace_create_global_tags(const char *pre_process, const char **i GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type, TMTagAttrType *attrs, TMParserType lang); -GPtrArray *tm_workspace_find_prefix(const char *prefix, TMParserType lang, guint max_num); +GPtrArray *tm_workspace_find_prefix(const char *prefix, + TMSourceFile *current_file, guint current_line, const gchar *current_scope, + TMParserType lang, guint max_num); GPtrArray *tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, gboolean function, gboolean member, const gchar *current_scope, gboolean search_namespace); From 25f150931e376d57654fb1df59b892c48af80e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Tue, 19 Apr 2022 19:14:01 +0200 Subject: [PATCH 04/11] Update scope completion to take into account local variables The code removes invalid local variables from other functions and behind current position. In addition, it sorts the found tags corresponding to the name in the editor for which we perform scope completion so local variables are searched first for their members, followed by tags from the current file, followed by workspace tags and finally using global tags. --- src/editor.c | 3 +- src/tagmanager/tm_workspace.c | 65 +++++++++++++++++++++++++++++++---- src/tagmanager/tm_workspace.h | 4 ++- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/editor.c b/src/editor.c index 986f5225f6..d38e27b210 100644 --- a/src/editor.c +++ b/src/editor.c @@ -698,6 +698,7 @@ static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize { ScintillaObject *sci = editor->sci; gint pos = sci_get_current_position(editor->sci); + gint line = sci_get_current_line(editor->sci) + 1; gchar typed = sci_get_char_at(sci, pos - 1); gchar brace_char; gchar *name; @@ -764,7 +765,7 @@ static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize if (symbols_get_current_scope(editor->document, ¤t_scope) == -1) current_scope = ""; tags = tm_workspace_find_scope_members(editor->document->tm_file, name, function, - member, current_scope, scope_sep_typed); + member, current_scope, line, scope_sep_typed); if (tags) { GPtrArray *filtered = g_ptr_array_new(); diff --git a/src/tagmanager/tm_workspace.c b/src/tagmanager/tm_workspace.c index d110e0499b..c9c358afdc 100644 --- a/src/tagmanager/tm_workspace.c +++ b/src/tagmanager/tm_workspace.c @@ -684,16 +684,19 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type } -static gboolean is_valid_autocomplete_tag(TMTag *tag, +gboolean tm_workspace_is_autocomplete_tag(TMTag *tag, TMSourceFile *current_file, guint current_line, const gchar *current_scope) { + TMParserType lang = current_file ? current_file->lang : TM_PARSER_NONE; + /* ignore local variables from other files/functions or after current line */ - return !(tag->type & tm_tag_local_var_t) || + gboolean valid = !(tag->type & tm_tag_local_var_t) || (current_file == tag->file && current_line >= tag->line && g_strcmp0(current_scope, tag->scope) == 0); + return valid && !tm_tag_is_anon(tag) && tm_parser_langs_compatible(lang, tag->lang); } @@ -714,11 +717,9 @@ static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src, tag = tm_tags_find(src, name, TRUE, &count); for (i = 0; i < count && num < max_num; ++i) { - if (is_valid_autocomplete_tag(*tag, current_file, current_line, current_scope)) + if (tm_workspace_is_autocomplete_tag(*tag, current_file, current_line, current_scope)) { - if (tm_parser_langs_compatible(lang, (*tag)->lang) && - !tm_tag_is_anon(*tag) && - (!last || g_strcmp0(last->name, (*tag)->name) != 0)) + if (!last || g_strcmp0(last->name, (*tag)->name) != 0) { g_ptr_array_add(dst, *tag); last = *tag; @@ -730,6 +731,39 @@ static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src, } +typedef struct +{ + TMSourceFile *file; +} SortInfo; + + +static gint sort_found_tags(gconstpointer a, gconstpointer b, gpointer user_data) +{ + SortInfo *info = user_data; + const TMTag *t1 = *((TMTag **) a); + const TMTag *t2 = *((TMTag **) b); + + /* sort local vars first (with highest line number first), followed + * by tags from current file, followed by workspace tags, followed by + * global tags */ + if (t1->type & tm_tag_local_var_t && t2->type & tm_tag_local_var_t) + return t2->line - t1->line; + else if (t1->type & tm_tag_local_var_t) + return -1; + else if (t2->type & tm_tag_local_var_t) + return 1; + else if (t1->file == info->file && t2->file != info->file) + return -1; + else if (t2->file == info->file && t1->file != info->file) + return 1; + else if (t1->file && !t2->file) + return -1; + else if (t2->file && !t1->file) + return 1; + return 0; +} + + /* Returns tags with the specified prefix sorted by name, ignoring local variables from other files/functions or after current line. If there are several tags with the same name, only one of them appears in the resulting array. @@ -1026,11 +1060,13 @@ static GPtrArray *find_namespace_members_all(const GPtrArray *tags, const GPtrAr @param function TRUE if the name is a name of a function @param member TRUE if invoked on class/struct member (e.g. after the last dot in foo.bar.) @param current_scope The current scope in the editor + @param current_line The current line in the editor @param search_namespace Whether to search the contents of namespace (e.g. after MyNamespace::) @return A GPtrArray of TMTag pointers to struct/union/class members or NULL when not found */ GPtrArray * tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, - gboolean function, gboolean member, const gchar *current_scope, gboolean search_namespace) + gboolean function, gboolean member, const gchar *current_scope, guint current_line, + gboolean search_namespace) { TMParserType lang = source_file ? source_file->lang : TM_PARSER_NONE; GPtrArray *tags, *member_tags = NULL; @@ -1053,12 +1089,27 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, if (!member_tags) { + SortInfo info; + guint i; + if (function) tag_type = function_types; /* tags corresponding to the variable/type name */ tags = tm_workspace_find(name, NULL, tag_type, NULL, lang); + /* remove invalid local tags and sort tags so "nearest" tags are first */ + for (i = 0; i < tags->len; i++) + { + TMTag *tag = tags->pdata[i]; + if (!tm_workspace_is_autocomplete_tag(tag, source_file, current_line, current_scope)) + tags->pdata[i] = NULL; + } + tm_tags_prune(tags); + + info.file = source_file; + g_ptr_array_sort_with_data(tags, sort_found_tags, &info); + /* Start searching inside the source file, continue with workspace tags and * end with global tags. This way we find the "closest" tag to the current * file in case there are more of them. */ diff --git a/src/tagmanager/tm_workspace.h b/src/tagmanager/tm_workspace.h index ccc78bfc01..d207763c28 100644 --- a/src/tagmanager/tm_workspace.h +++ b/src/tagmanager/tm_workspace.h @@ -61,7 +61,7 @@ GPtrArray *tm_workspace_find_prefix(const char *prefix, TMParserType lang, guint max_num); GPtrArray *tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, - gboolean function, gboolean member, const gchar *current_scope, gboolean search_namespace); + gboolean function, gboolean member, const gchar *current_scope, guint current_line, gboolean search_namespace); void tm_workspace_add_source_file_noupdate(TMSourceFile *source_file); @@ -71,6 +71,8 @@ void tm_workspace_update_source_file_buffer(TMSourceFile *source_file, guchar* t void tm_workspace_free(void); +gboolean tm_workspace_is_autocomplete_tag(TMTag *tag, TMSourceFile *current_file, + guint current_line, const gchar *current_scope); #ifdef TM_DEBUG void tm_workspace_dump(void); From 13bdb37cf7496a27b0b77c9893839c0fc3aba186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Mon, 18 Apr 2022 00:38:12 +0200 Subject: [PATCH 05/11] Update goto symbol definitions to take into account local variables We only want to use local variables within the current function for the goto. This means we want to filter out local variable tags that: 1. Are from a different file 2. Are declared later in the file than where the current cursor is 3. Have different scope than the current function scope Fundamentally the same requirements as for (non-scope) autocompletion. --- src/symbols.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/symbols.c b/src/symbols.c index 51a5a93f1f..d211b505a1 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -1597,13 +1597,23 @@ static TMTag *find_best_goto_tag(GeanyDocument *doc, GPtrArray *tags) static GPtrArray *filter_tags(GPtrArray *tags, TMTag *current_tag, gboolean definition) { + GeanyDocument *doc = document_get_current(); + guint current_line = sci_get_current_line(doc->editor->sci) + 1; const TMTagType forward_types = tm_tag_prototype_t | tm_tag_externvar_t; TMTag *tmtag, *last_tag = NULL; + const gchar *current_scope = NULL; GPtrArray *filtered_tags = g_ptr_array_new(); guint i; + symbols_get_current_function(doc, ¤t_scope); + foreach_ptr_array(tmtag, i, tags) { + /* don't show local variables outside current function or other + * irrelevant tags - same as in the autocomplete case */ + if (!tm_workspace_is_autocomplete_tag(tmtag, doc->tm_file, current_line, current_scope)) + continue; + if ((definition && !(tmtag->type & forward_types)) || (!definition && (tmtag->type & forward_types))) { From 2cf2e87bda17de13fea495b965816b9c19aec920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Tue, 19 Apr 2022 19:17:57 +0200 Subject: [PATCH 06/11] Strip more things from variable type We are interested in pure type name and ctags returns types including pointers, references, arrays, template parameters and keywords such as "const" or "struct". Strip all those. Also move strip_type() above so it's usable by other functions. --- src/tagmanager/tm_workspace.c | 92 +++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/src/tagmanager/tm_workspace.c b/src/tagmanager/tm_workspace.c index c9c358afdc..ac550357fd 100644 --- a/src/tagmanager/tm_workspace.c +++ b/src/tagmanager/tm_workspace.c @@ -795,6 +795,79 @@ GPtrArray *tm_workspace_find_prefix(const char *prefix, } +static gboolean replace_with_char(gchar *haystack, const gchar *needle, char replacement) +{ + gchar *pos = strstr(haystack, needle); + if (pos) + { + while (*needle) + { + *pos = replacement; + needle++; + pos++; + } + return TRUE; + } + return FALSE; +} + + +static gboolean replace_parens_with_char(gchar *haystack, gchar paren_begin, gchar paren_end, char replacement) +{ + gchar needle[2] = {paren_begin, '\0'}; + gchar *pos = strstr(haystack, needle); + gint nesting = 0; + + if (pos) + { + while (*pos) + { + if (*pos == paren_begin) + nesting++; + else if (*pos == paren_end) + nesting--; + *pos = replacement; + if (nesting == 0) + break; + pos++; + } + return TRUE; + } + return FALSE; +} + + +static gchar *strip_type(const gchar *scoped_name, TMParserType lang) +{ + if (scoped_name != NULL) + { + const gchar *sep = tm_parser_scope_separator(lang); + gchar *name = g_strdup(scoped_name); + gchar *scope_suffix; + + /* remove pointers, parens and keywords appearing in types */ + g_strdelimit(name, "*^&", ' '); + while (replace_parens_with_char(name, '[', ']', ' ')) {} + while (replace_parens_with_char(name, '<', '>', ' ')) {} + while (replace_with_char(name, "const ", ' ')) {} + while (replace_with_char(name, " const", ' ')) {} + while (replace_with_char(name, " struct", ' ')) {} + /* remove everything before final scope separator */ + if (scope_suffix = g_strrstr(name, sep)) + { + scope_suffix += strlen(sep); + scope_suffix = g_strdup(scope_suffix); + g_free(name); + name = scope_suffix; + } + g_strstrip(name); + + return name; + } + return NULL; +} + + /* Gets all members of type_tag; search them inside the all array. * The namespace parameter determines whether we are performing the "namespace" * search (user has typed something like "A::" where A is a type) or "scope" search @@ -844,25 +917,6 @@ find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespa } -static gchar *strip_type(const gchar *scoped_name, TMParserType lang) -{ - if (scoped_name != NULL) - { - /* remove scope prefix */ - const gchar *sep = tm_parser_scope_separator(lang); - const gchar *base = g_strrstr(scoped_name, sep); - gchar *name = base ? g_strdup(base + strlen(sep)) : g_strdup(scoped_name); - - /* remove pointers */ - g_strdelimit(name, "*^", ' '); - g_strstrip(name); - - return name; - } - return NULL; -} - - /* Gets all members of the type with the given name; search them inside tags_array */ static GPtrArray * find_scope_members (const GPtrArray *tags_array, const gchar *name, TMSourceFile *file, From e4f2ed745238708673bef4c9a65e5e933f2215b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Wed, 20 Apr 2022 21:27:55 +0200 Subject: [PATCH 07/11] Support (multiple) inheritance by scope completion The code simply checks for inherited classes, strips unneeded stuff like templates, etc. from inherited classes, detects multiple inheritance by splitting the string using "," and calling tm_workspace_get_parents() recursively on every parent class and collecting the returned tags. The code limits the recursion depth to 10 to avoid possible inheritance cycles and infinite recursion. Also remove #if 0'd old code from TM implementing inheritance that we kept for reference as we now have a better implementation. --- src/tagmanager/tm_workspace.c | 126 ++++++++++++++++------------------ 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/src/tagmanager/tm_workspace.c b/src/tagmanager/tm_workspace.c index ac550357fd..de9bb29037 100644 --- a/src/tagmanager/tm_workspace.c +++ b/src/tagmanager/tm_workspace.c @@ -837,7 +837,7 @@ static gboolean replace_parens_with_char(gchar *haystack, gchar paren_begin, gch } -static gchar *strip_type(const gchar *scoped_name, TMParserType lang) +static gchar *strip_type(const gchar *scoped_name, TMParserType lang, gboolean remove_scope) { if (scoped_name != NULL) { @@ -853,7 +853,7 @@ static gchar *strip_type(const gchar *scoped_name, TMParserType lang) while (replace_with_char(name, " const", ' ')) {} while (replace_with_char(name, " struct", ' ')) {} /* remove everything before final scope separator */ - if (scope_suffix = g_strrstr(name, sep)) + if (remove_scope && (scope_suffix = g_strrstr(name, sep))) { scope_suffix += strlen(sep); scope_suffix = g_strdup(scope_suffix); @@ -876,13 +876,18 @@ static gchar *strip_type(const gchar *scoped_name, TMParserType lang) * scope search we return only those which can be invoked on a variable (member, * method, etc.). */ static GPtrArray * -find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespace) +find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespace, guint depth) { TMTagType member_types = tm_tag_max_t & ~(TM_TYPE_WITH_MEMBERS | tm_tag_typedef_t); - GPtrArray *tags = g_ptr_array_new(); + GPtrArray *tags; gchar *scope; guint i; + if (depth == 10) + return NULL; /* simple inheritence cycle avoidance */ + + tags = g_ptr_array_new(); + if (namespace) member_types = tm_tag_max_t; @@ -905,6 +910,47 @@ find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespa } } + /* add members from parent classes */ + if (!namespace && (type_tag->type & (tm_tag_class_t | tm_tag_struct_t)) && + type_tag->inheritance && *type_tag->inheritance) + { + gchar *stripped = strip_type(type_tag->inheritance, type_tag->lang, FALSE); + gchar **split_strv = g_strsplit(stripped, ",", -1); /* parent classes */ + const gchar *parent; + + g_free(stripped); + + for (i = 0; parent = split_strv[i]; i++) + { + GPtrArray *parent_tags; + + stripped = strip_type(parent, type_tag->lang, TRUE); + parent_tags = tm_workspace_find(stripped, NULL, tm_tag_class_t | tm_tag_struct_t, + NULL, type_tag->lang); + + if (parent_tags->len > 0) + { + TMTag *parent_tag = parent_tags->pdata[0]; + GPtrArray *parent_members = find_scope_members_tags( + parent_tag->file ? parent_tag->file->tags_array : all, parent_tag, + FALSE, depth + 1); + + if (parent_members) + { + guint j; + for (j = 0; j < parent_members->len; j++) + g_ptr_array_add (tags, parent_members->pdata[j]); + g_ptr_array_free(parent_members, TRUE); + } + } + + g_ptr_array_free(parent_tags, TRUE); + g_free(stripped); + } + + g_strfreev(split_strv); + } + g_free(scope); if (tags->len == 0) @@ -913,6 +959,12 @@ find_scope_members_tags (const GPtrArray *all, TMTag *type_tag, gboolean namespa return NULL; } + if (depth == 0) + { + TMTagAttrType sort_attrs[] = {tm_tag_attr_name_t, 0}; + tm_tags_sort(tags, sort_attrs, TRUE, FALSE); + } + return tags; } @@ -975,7 +1027,7 @@ find_scope_members (const GPtrArray *tags_array, const gchar *name, TMSourceFile if (tag->var_type && tag->var_type[0] != '\0') { g_free(type_name); - type_name = strip_type(tag->var_type, tag->lang); + type_name = strip_type(tag->var_type, tag->lang, TRUE); file = tag->file; continue; } @@ -984,7 +1036,7 @@ find_scope_members (const GPtrArray *tags_array, const gchar *name, TMSourceFile else /* real type with members */ { /* use the same file as the composite type if file information available */ - res = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace); + res = find_scope_members_tags(tag->file ? tag->file->tags_array : tags_array, tag, namespace, 0); break; } } @@ -1067,7 +1119,7 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, T member_tags = find_scope_members(searched_array, tag->name, tag->file, lang, TRUE); else member_tags = find_scope_members_tags(tag->file ? tag->file->tags_array : searched_array, - tag, TRUE); + tag, TRUE, 0); } else if (tag->var_type) /* variable: scope search */ { @@ -1079,7 +1131,7 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, T if (!(tag->type & member_types) || member || member_at_method_scope(tags, current_scope, tag, lang)) { - gchar *tag_type = strip_type(tag->var_type, tag->lang); + gchar *tag_type = strip_type(tag->var_type, tag->lang, TRUE); member_tags = find_scope_members(searched_array, tag_type, tag->file, lang, FALSE); g_free(tag_type); @@ -1100,7 +1152,7 @@ static GPtrArray *find_namespace_members_all(const GPtrArray *tags, const GPtrAr { TMTag *tag = TM_TAG(tags->pdata[i]); - member_tags = find_scope_members_tags(searched_array, tag, TRUE); + member_tags = find_scope_members_tags(searched_array, tag, TRUE, 0); } return member_tags; @@ -1204,59 +1256,3 @@ void tm_workspace_dump(void) } } #endif /* TM_DEBUG */ - - -#if 0 - -/* Returns a list of parent classes for the given class name - @param name Name of the class - @return A GPtrArray of TMTag pointers (includes the TMTag for the class) */ -static const GPtrArray *tm_workspace_get_parents(const gchar *name) -{ - static TMTagAttrType type[] = { tm_tag_attr_name_t, tm_tag_attr_none_t }; - static GPtrArray *parents = NULL; - const GPtrArray *matches; - guint i = 0; - guint j; - gchar **klasses; - gchar **klass; - TMTag *tag; - - g_return_val_if_fail(name && isalpha(*name),NULL); - - if (NULL == parents) - parents = g_ptr_array_new(); - else - g_ptr_array_set_size(parents, 0); - matches = tm_workspace_find(name, NULL, tm_tag_class_t, type, -1); - if ((NULL == matches) || (0 == matches->len)) - return NULL; - g_ptr_array_add(parents, matches->pdata[0]); - while (i < parents->len) - { - tag = TM_TAG(parents->pdata[i]); - if ((NULL != tag->inheritance) && (isalpha(tag->inheritance[0]))) - { - klasses = g_strsplit(tag->inheritance, ",", 10); - for (klass = klasses; (NULL != *klass); ++ klass) - { - for (j=0; j < parents->len; ++j) - { - if (0 == strcmp(*klass, TM_TAG(parents->pdata[j])->name)) - break; - } - if (parents->len == j) - { - matches = tm_workspace_find(*klass, NULL, tm_tag_class_t, type, -1); - if ((NULL != matches) && (0 < matches->len)) - g_ptr_array_add(parents, matches->pdata[0]); - } - } - g_strfreev(klasses); - } - ++ i; - } - return parents; -} - -#endif From 2a5da2225f7aa87bdc7f82c3966f6fb32754ae5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 23 Apr 2022 22:25:08 +0200 Subject: [PATCH 08/11] Rewrite member_at_method_scope() to handle more situations When performing scope completion for void A::foo() { bar. // <-- } we need to determine what 'bar' is. It could be a global variable, in which case we should look for variable tags, it could however also be a member of A in which case we should look for member tags. To determine this, the previous code checked whether there's a tag named 'bar' with the same scope as the method tag. One drawback of this approach is that it doesn't take namespace manipulation functions like 'using namespace' into account so for header namespace X { class A { Baz bar; }; }; and source using namespace X; void A::foo() { bar. // <-- } it wouldn't find 'bar' because ctags reports foo() to have scope A and bar to have scope X::A. Another drawback of this approach is that it doesn't take inheritance into account so it wouldn't find 'bar' when defined in a super-class, such as class B { Baz bar; }; class A : B { void foo(); } To avoid these problems, this patch rewrites member_at_method_scope() (and renames it to member_accessible()) so it gets the class name from the scope of the method in which we are (A in the above example) and returns all members of A (including the super-classes members). Afterwards, it checks if one of the members is really the member tag we are interested in ('bar' in the above example). --- src/tagmanager/tm_workspace.c | 54 ++++++++++++++++------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/tagmanager/tm_workspace.c b/src/tagmanager/tm_workspace.c index de9bb29037..ef15e46062 100644 --- a/src/tagmanager/tm_workspace.c +++ b/src/tagmanager/tm_workspace.c @@ -1047,8 +1047,8 @@ find_scope_members (const GPtrArray *tags_array, const gchar *name, TMSourceFile } -/* Checks whether a member tag is directly accessible from method with method_scope */ -static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *method_scope, TMTag *member_tag, +/* Checks whether a member tag is directly accessible from method */ +static gboolean member_accessible(const GPtrArray *tags, const gchar *method_scope, TMTag *member_tag, TMParserType lang) { const gchar *sep = tm_parser_scope_separator(lang); @@ -1061,35 +1061,31 @@ static gboolean member_at_method_scope(const GPtrArray *tags, const gchar *metho len = g_strv_length(comps); if (len > 1) { - gchar *method, *member_scope, *cls, *cls_scope; - - /* get method/member scope */ - method = comps[len - 1]; - comps[len - 1] = NULL; - member_scope = g_strjoinv(sep, comps); - comps[len - 1] = method; - - /* get class scope */ - cls = comps[len - 2]; - comps[len - 2] = NULL; - cls_scope = g_strjoinv(sep, comps); - comps[len - 2] = cls; - cls_scope = strlen(cls_scope) > 0 ? cls_scope : NULL; - - /* check whether member inside the class */ - if (g_strcmp0(member_tag->scope, member_scope) == 0) + gchar *cls = comps[len - 2]; + + if (*cls) { - const GPtrArray *src = member_tag->file ? member_tag->file->tags_array : tags; - GPtrArray *cls_tags = g_ptr_array_new(); + /* find method's class members */ + GPtrArray *cls_tags = find_scope_members(tags, cls, NULL, lang, FALSE); - /* check whether the class exists */ - fill_find_tags_array(cls_tags, src, cls, cls_scope, TM_TYPE_WITH_MEMBERS | tm_tag_namespace_t, lang); - ret = cls_tags->len > 0; - g_ptr_array_free(cls_tags, TRUE); - } + if (cls_tags) + { + guint i; + + /* check if one of the class members is member_tag */ + for (i = 0; i < cls_tags->len; i++) + { + TMTag *t = cls_tags->pdata[i]; - g_free(cls_scope); - g_free(member_scope); + if (t == member_tag) + { + ret = TRUE; + break; + } + } + g_ptr_array_free(cls_tags, TRUE); + } + } } g_strfreev(comps); @@ -1129,7 +1125,7 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, T * inside a method where foo is a class member, we want scope completion * for foo. */ if (!(tag->type & member_types) || member || - member_at_method_scope(tags, current_scope, tag, lang)) + member_accessible(searched_array, current_scope, tag, lang)) { gchar *tag_type = strip_type(tag->var_type, tag->lang, TRUE); From 4f45f7014b3e7aabf650a68153ea38db4ffc5fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sun, 24 Apr 2022 19:32:59 +0200 Subject: [PATCH 09/11] Sort non-scope autocompletion results by locality To present more relevant results at the top of the list, sort them in the following order: local variables tags from current file workspace tags global tags and alphabetically within a single group. In addition, we need to remove duplicates from the list of displayed names. Finally, since the entries are not sorted alphabetically, we need to call SSM(sci, SCI_AUTOCSETORDER, SC_ORDER_CUSTOM, 0); so Scintilla knows it shouldn't sort the list alphabetically. --- src/editor.c | 1 + src/tagmanager/tm_workspace.c | 127 ++++++++++++++++++++++++++-------- 2 files changed, 99 insertions(+), 29 deletions(-) diff --git a/src/editor.c b/src/editor.c index d38e27b210..ac81e7eb81 100644 --- a/src/editor.c +++ b/src/editor.c @@ -601,6 +601,7 @@ static void show_autocomplete(ScintillaObject *sci, gsize rootlen, GString *word } /* store whether a calltip is showing, so we can reshow it after autocompletion */ calltip.set = (gboolean) SSM(sci, SCI_CALLTIPACTIVE, 0, 0); + SSM(sci, SCI_AUTOCSETORDER, SC_ORDER_CUSTOM, 0); SSM(sci, SCI_AUTOCSHOW, rootlen, (sptr_t) words->str); } diff --git a/src/tagmanager/tm_workspace.c b/src/tagmanager/tm_workspace.c index ef15e46062..0f9c92c492 100644 --- a/src/tagmanager/tm_workspace.c +++ b/src/tagmanager/tm_workspace.c @@ -700,40 +700,103 @@ gboolean tm_workspace_is_autocomplete_tag(TMTag *tag, } -static void fill_find_tags_array_prefix(GPtrArray *dst, const GPtrArray *src, - const char *name, - TMSourceFile *current_file, - guint current_line, - const gchar *current_scope, - TMParserType lang, guint max_num) +typedef struct { - TMTag **tag, *last = NULL; - guint i, count, num; + TMSourceFile *file; + guint line; + const gchar *scope; + TMParserType lang; +} CopyInfo; - if (!src || !dst || !name || !*name) +static gboolean is_any_tag(TMTag *tag, CopyInfo *info) +{ + return TRUE; +} + +static gboolean is_local_tag(TMTag *tag, CopyInfo *info) +{ + return tag->type & tm_tag_local_var_t; +} + +static gboolean is_non_local_tag(TMTag *tag, CopyInfo *info) +{ + return !is_local_tag(tag, info); +} + +/* non-local tag not from current file */ +static gboolean is_workspace_tag(TMTag *tag, CopyInfo *info) +{ + return tag->file != info->file && + is_non_local_tag(tag, info); +} + + +static guint copy_tags(GPtrArray *dst, TMTag **src, guint src_len, GHashTable *name_table, + gint num, gboolean (*predicate) (TMTag *, CopyInfo *), CopyInfo *info) +{ + guint i; + + g_return_val_if_fail(src && dst, 0); + + for (i = 0; i < src_len && num > 0; i++) + { + TMTag *tag = *src; + if (predicate(tag, info) && + tm_workspace_is_autocomplete_tag(tag, info->file, info->line, info->scope) && + !g_hash_table_contains(name_table, tag->name)) + { + g_ptr_array_add(dst, tag); + g_hash_table_add(name_table, tag->name); + num--; + } + src++; + } +} + + +static void fill_find_tags_array_prefix(GPtrArray *dst, const char *name, + CopyInfo *info, guint max_num) +{ + TMTag **found; + guint count; + GHashTable *name_table; + + if (!dst || !name || !*name) return; - num = 0; - tag = tm_tags_find(src, name, TRUE, &count); - for (i = 0; i < count && num < max_num; ++i) + name_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); + + if (info->file) { - if (tm_workspace_is_autocomplete_tag(*tag, current_file, current_line, current_scope)) + found = tm_tags_find(info->file->tags_array, name, TRUE, &count); + if (found) { - if (!last || g_strcmp0(last->name, (*tag)->name) != 0) - { - g_ptr_array_add(dst, *tag); - last = *tag; - num++; - } + copy_tags(dst, found, count, name_table, max_num - dst->len, is_local_tag, info); + if (dst->len < max_num) + copy_tags(dst, found, count, name_table, max_num - dst->len, is_non_local_tag, info); } - tag++; } + if (dst->len < max_num) + { + found = tm_tags_find(theWorkspace->tags_array, name, TRUE, &count); + if (found) + copy_tags(dst, found, count, name_table, max_num - dst->len, is_workspace_tag, info); + } + if (dst->len < max_num) + { + found = tm_tags_find(theWorkspace->global_tags, name, TRUE, &count); + if (found) + copy_tags(dst, found, count, name_table, max_num - dst->len, is_any_tag, info); + } + + g_hash_table_unref(name_table); } typedef struct { TMSourceFile *file; + gboolean sort_by_name; } SortInfo; @@ -747,7 +810,7 @@ static gint sort_found_tags(gconstpointer a, gconstpointer b, gpointer user_data * by tags from current file, followed by workspace tags, followed by * global tags */ if (t1->type & tm_tag_local_var_t && t2->type & tm_tag_local_var_t) - return t2->line - t1->line; + return info->sort_by_name ? g_strcmp0(t1->name, t2->name) : t2->line - t1->line; else if (t1->type & tm_tag_local_var_t) return -1; else if (t2->type & tm_tag_local_var_t) @@ -760,7 +823,7 @@ static gint sort_found_tags(gconstpointer a, gconstpointer b, gpointer user_data return -1; else if (t2->file && !t1->file) return 1; - return 0; + return g_strcmp0(t1->name, t2->name); } @@ -781,15 +844,20 @@ GPtrArray *tm_workspace_find_prefix(const char *prefix, { TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 }; GPtrArray *tags = g_ptr_array_new(); + SortInfo sort_info; + CopyInfo copy_info; - fill_find_tags_array_prefix(tags, theWorkspace->tags_array, prefix, - current_file, current_line, current_scope, lang, max_num); - fill_find_tags_array_prefix(tags, theWorkspace->global_tags, prefix, - current_file, current_line, current_scope, lang, max_num); + copy_info.file = current_file; + copy_info.line = current_line; + copy_info.scope = current_scope; + copy_info.lang = lang; + fill_find_tags_array_prefix(tags, prefix, ©_info, max_num); - tm_tags_sort(tags, attrs, TRUE, FALSE); - if (tags->len > max_num) - tags->len = max_num; + /* sort based on how "close" the tag is to current line with local + * variables first */ + sort_info.file = current_file; + sort_info.sort_by_name = TRUE; + g_ptr_array_sort_with_data(tags, sort_found_tags, &sort_info); return tags; } @@ -1210,6 +1278,7 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, tm_tags_prune(tags); info.file = source_file; + info.sort_by_name = FALSE; g_ptr_array_sort_with_data(tags, sort_found_tags, &info); /* Start searching inside the source file, continue with workspace tags and From db1c862d5efc3a6a8aadf4232dda2d0093455f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 27 Aug 2022 23:48:57 +0200 Subject: [PATCH 10/11] Remove unnecessary "lang" parameter of various functions In functions where we pass the current file as an argument, we can get the used language from the file and this extra parameter is unnecessary. --- src/editor.c | 2 +- src/tagmanager/tm_workspace.c | 10 ++++------ src/tagmanager/tm_workspace.h | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/editor.c b/src/editor.c index ac81e7eb81..04a6f9b4dc 100644 --- a/src/editor.c +++ b/src/editor.c @@ -2033,7 +2033,7 @@ autocomplete_tags(GeanyEditor *editor, GeanyFiletype *ft, const gchar *root, gsi current_line = sci_get_current_line(editor->sci) + 1; tags = tm_workspace_find_prefix(root, doc->tm_file, current_line, current_scope, - ft->lang, editor_prefs.autocompletion_max_entries); + editor_prefs.autocompletion_max_entries); found = tags->len > 0; if (found) show_tags_list(editor, tags, rootlen); diff --git a/src/tagmanager/tm_workspace.c b/src/tagmanager/tm_workspace.c index 0f9c92c492..3511e8ea5b 100644 --- a/src/tagmanager/tm_workspace.c +++ b/src/tagmanager/tm_workspace.c @@ -705,7 +705,6 @@ typedef struct TMSourceFile *file; guint line; const gchar *scope; - TMParserType lang; } CopyInfo; static gboolean is_any_tag(TMTag *tag, CopyInfo *info) @@ -840,7 +839,7 @@ GPtrArray *tm_workspace_find_prefix(const char *prefix, TMSourceFile *current_file, guint current_line, const gchar *current_scope, - TMParserType lang, guint max_num) + guint max_num) { TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 }; GPtrArray *tags = g_ptr_array_new(); @@ -850,7 +849,6 @@ GPtrArray *tm_workspace_find_prefix(const char *prefix, copy_info.file = current_file; copy_info.line = current_line; copy_info.scope = current_scope; - copy_info.lang = lang; fill_find_tags_array_prefix(tags, prefix, ©_info, max_num); /* sort based on how "close" the tag is to current line with local @@ -1207,7 +1205,7 @@ find_scope_members_all(const GPtrArray *tags, const GPtrArray *searched_array, T } -static GPtrArray *find_namespace_members_all(const GPtrArray *tags, const GPtrArray *searched_array, TMParserType lang) +static GPtrArray *find_namespace_members_all(const GPtrArray *tags, const GPtrArray *searched_array) { GPtrArray *member_tags = NULL; guint i; @@ -1250,9 +1248,9 @@ tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, { tags = tm_workspace_find(name, NULL, tm_tag_namespace_t, NULL, lang); - member_tags = find_namespace_members_all(tags, theWorkspace->tags_array, lang); + member_tags = find_namespace_members_all(tags, theWorkspace->tags_array); if (!member_tags) - member_tags = find_namespace_members_all(tags, theWorkspace->global_tags, lang); + member_tags = find_namespace_members_all(tags, theWorkspace->global_tags); g_ptr_array_free(tags, TRUE); } diff --git a/src/tagmanager/tm_workspace.h b/src/tagmanager/tm_workspace.h index d207763c28..98e857558f 100644 --- a/src/tagmanager/tm_workspace.h +++ b/src/tagmanager/tm_workspace.h @@ -58,7 +58,7 @@ GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type GPtrArray *tm_workspace_find_prefix(const char *prefix, TMSourceFile *current_file, guint current_line, const gchar *current_scope, - TMParserType lang, guint max_num); + guint max_num); GPtrArray *tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name, gboolean function, gboolean member, const gchar *current_scope, guint current_line, gboolean search_namespace); From a91a9c6ca17e5a7a163b095d5a9065afa7e8af77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 11 Jun 2022 19:55:22 +0200 Subject: [PATCH 11/11] Update documentation related to scope autocompletion --- doc/geany.txt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/geany.txt b/doc/geany.txt index 4c2b006a78..2ac4915b4e 100644 --- a/doc/geany.txt +++ b/doc/geany.txt @@ -937,6 +937,10 @@ preferences`_, default 4) or when the *Complete word* keybinding is pressed (configurable, see `Editor keybindings`_, default Ctrl-Space). +For some languages the autocompletion list is ordered by heuristics to +attempt to show names that are more likely to be what the user wants +close to the top of the list. + When the defined keybinding is typed and the *Autocomplete all words in document* preference (in `Editor Completions preferences`_) is selected then the autocompletion list will show all matching words @@ -986,9 +990,11 @@ When you type ``foo.`` it will show an autocompletion list with 'i' and 'c' symbols. It only works for languages that set parent scope names for e.g. struct -members. Currently this means C-like languages. The C parser only -parses global scopes, so this won't work for structs or objects declared -in local scope. +members. Most languages only parse global definitions and so scope +autocompletion will not work for names declared in local scope +(e.g. inside functions). A few languages parse both local and global +symbols (e.g. C/C++ parsers) and for these parsers scope autocompletion +works also for local variables. User-definable snippets