Merge pull request #862 from techee/tm_workspace_find_cleanup3

Rewrite scope completion v3.

Closes #488 and #505.
This commit is contained in:
Colomban Wendling 2016-02-11 15:30:09 +01:00
commit a775da0714
7 changed files with 598 additions and 734 deletions

View File

@ -75,6 +75,7 @@ static GHashTable *snippet_hash = NULL;
static GQueue *snippet_offsets = NULL;
static gint snippet_cursor_insert_pos;
static GtkAccelGroup *snippet_accel_group = NULL;
static gboolean autocomplete_scope_shown = FALSE;
static const gchar geany_cursor_marker[] = "__GEANY_CURSOR_MARKER__";
@ -657,7 +658,9 @@ static gboolean match_last_chars(ScintillaObject *sci, gint pos, const gchar *st
gchar *buf;
g_return_val_if_fail(len < 100, FALSE);
g_return_val_if_fail((gint)len <= pos, FALSE);
if ((gint)len > pos)
return FALSE;
buf = g_alloca(len + 1);
sci_get_text_range(sci, pos - len, pos, buf);
@ -697,51 +700,108 @@ static void request_reshowing_calltip(SCNotification *nt)
}
static void autocomplete_scope(GeanyEditor *editor)
static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize rootlen)
{
ScintillaObject *sci = editor->sci;
gint pos = sci_get_current_position(editor->sci);
gchar typed = sci_get_char_at(sci, pos - 1);
gchar brace_char;
gchar *name;
const GPtrArray *tags = NULL;
const TMTag *tag;
GeanyFiletype *ft = editor->document->file_type;
GPtrArray *tags;
gboolean function = FALSE;
gboolean member;
gboolean ret = FALSE;
const gchar *current_scope;
const gchar *context_sep = tm_tag_context_separator(ft->lang);
if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP)
if (autocomplete_scope_shown)
{
if (pos >= 2 && (match_last_chars(sci, pos, "->") || match_last_chars(sci, pos, "::")))
/* move at the operator position */
pos -= rootlen;
/* allow for a space between word and operator */
while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
pos--;
else if (ft->id == GEANY_FILETYPES_CPP && pos >= 3 && match_last_chars(sci, pos, "->*"))
pos-=2;
else if (typed != '.')
return;
if (pos > 0)
typed = sci_get_char_at(sci, pos - 1);
}
else if (typed != '.')
return;
/* make sure to keep in sync with similar checks below */
if (typed == '.')
pos -= 1;
else if (match_last_chars(sci, pos, context_sep))
pos -= strlen(context_sep);
else if ((ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP) &&
match_last_chars(sci, pos, "->"))
pos -= 2;
else if (ft->id == GEANY_FILETYPES_CPP && match_last_chars(sci, pos, "->*"))
pos -= 3;
else
return FALSE;
/* allow for a space between word and operator */
if (isspace(sci_get_char_at(sci, pos - 2)))
while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
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, ft->lang);
g_free(name);
if (!tags || tags->len == 0)
return;
tag = g_ptr_array_index(tags, 0);
name = tag->var_type;
if (name)
/* if function or array index, skip to matching brace */
brace_char = sci_get_char_at(sci, pos - 1);
if (pos > 0 && (brace_char == ')' || brace_char == ']'))
{
TMSourceFile *obj = editor->document->tm_file;
gint brace_pos = sci_find_matching_brace(sci, pos - 1);
tags = tm_workspace_find_scope_members(obj ? obj->tags_array : NULL,
name, TRUE, FALSE);
if (tags)
show_tags_list(editor, tags, 0);
if (brace_pos != -1)
{
pos = brace_pos;
function = brace_char == ')';
}
/* allow for a space between opening brace and name */
while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
pos--;
}
name = editor_get_word_at_pos(editor, pos, NULL);
if (!name)
return FALSE;
/* check if invoked on member */
pos -= strlen(name);
while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
pos--;
/* make sure to keep in sync with similar checks above */
member = match_last_chars(sci, pos, ".") || match_last_chars(sci, pos, context_sep) ||
match_last_chars(sci, pos, "->") || match_last_chars(sci, pos, "->*");
if (symbols_get_current_scope(editor->document, &current_scope) == -1)
current_scope = "";
tags = tm_workspace_find_scope_members(editor->document->tm_file, name, function,
member, current_scope);
if (tags)
{
GPtrArray *filtered = g_ptr_array_new();
TMTag *tag;
guint i;
foreach_ptr_array(tag, i, tags)
{
if (g_str_has_prefix(tag->name, root))
g_ptr_array_add(filtered, tag);
}
if (filtered->len > 0)
{
show_tags_list(editor, filtered, rootlen);
ret = TRUE;
}
g_ptr_array_free(tags, TRUE);
g_ptr_array_free(filtered, TRUE);
}
g_free(name);
return ret;
}
@ -1100,6 +1160,7 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *object, GeanyEditor *edi
case SCN_AUTOCCANCELLED:
/* now that autocomplete is finishing or was cancelled, reshow calltips
* if they were showing */
autocomplete_scope_shown = FALSE;
request_reshowing_calltip(nt);
break;
case SCN_NEEDSHOWN:
@ -1832,10 +1893,9 @@ static gboolean append_calltip(GString *str, const TMTag *tag, GeanyFiletypeID f
static gchar *find_calltip(const gchar *word, GeanyFiletype *ft)
{
const GPtrArray *tags;
GPtrArray *tags;
const TMTagType arg_types = tm_tag_function_t | tm_tag_prototype_t |
tm_tag_method_t | tm_tag_macro_with_arg_t;
TMTagAttrType *attrs = NULL;
TMTag *tag;
GString *str = NULL;
guint i;
@ -1843,20 +1903,26 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft)
g_return_val_if_fail(ft && word && *word, NULL);
/* use all types in case language uses wrong tag type e.g. python "members" instead of "methods" */
tags = tm_workspace_find(word, tm_tag_max_t, attrs, FALSE, ft->lang);
tags = tm_workspace_find(word, NULL, tm_tag_max_t, NULL, ft->lang);
if (tags->len == 0)
{
g_ptr_array_free(tags, TRUE);
return NULL;
}
tag = TM_TAG(tags->pdata[0]);
if (ft->id == GEANY_FILETYPES_D &&
(tag->type == tm_tag_class_t || tag->type == tm_tag_struct_t))
{
g_ptr_array_free(tags, TRUE);
/* user typed e.g. 'new Classname(' so lookup D constructor Classname::this() */
tags = tm_workspace_find_scoped("this", tag->name,
arg_types, attrs, FALSE, ft->lang, TRUE);
tags = tm_workspace_find("this", tag->name, arg_types, NULL, ft->lang);
if (tags->len == 0)
{
g_ptr_array_free(tags, TRUE);
return NULL;
}
}
/* remove tags with no argument list */
@ -1869,7 +1935,10 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft)
}
tm_tags_prune((GPtrArray *) tags);
if (tags->len == 0)
{
g_ptr_array_free(tags, TRUE);
return NULL;
}
else
{ /* remove duplicate calltips */
TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, tm_tag_attr_scope_t,
@ -1906,6 +1975,9 @@ static gchar *find_calltip(const gchar *word, GeanyFiletype *ft)
break;
}
}
g_ptr_array_free(tags, TRUE);
if (str)
{
gchar *result = str->str;
@ -2027,21 +2099,21 @@ autocomplete_html(ScintillaObject *sci, const gchar *root, gsize rootlen)
static gboolean
autocomplete_tags(GeanyEditor *editor, const gchar *root, gsize rootlen)
{
TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 };
const GPtrArray *tags;
GPtrArray *tags;
GeanyDocument *doc;
gboolean found;
g_return_val_if_fail(editor, FALSE);
doc = editor->document;
tags = tm_workspace_find(root, tm_tag_max_t, attrs, TRUE, doc->file_type->lang);
if (tags)
{
tags = tm_workspace_find_prefix(root, doc->file_type->lang, editor_prefs.autocompletion_max_entries);
found = tags->len > 0;
if (found)
show_tags_list(editor, tags, rootlen);
return tags->len > 0;
}
return FALSE;
g_ptr_array_free(tags, TRUE);
return found;
}
@ -2201,7 +2273,6 @@ gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean forc
if (!force && !highlighting_is_code_style(lexer, style))
return FALSE;
autocomplete_scope(editor);
ret = autocomplete_check_html(editor, style, pos);
if (ft->id == GEANY_FILETYPES_LATEX)
@ -2215,6 +2286,23 @@ gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean forc
root = cword;
rootlen = strlen(root);
if (ret || force)
{
if (autocomplete_scope_shown)
{
autocomplete_scope_shown = FALSE;
if (!ret)
sci_send_command(sci, SCI_AUTOCCANCEL);
}
}
else
{
ret = autocomplete_scope(editor, root, rootlen);
if (!ret && autocomplete_scope_shown)
sci_send_command(sci, SCI_AUTOCCANCEL);
autocomplete_scope_shown = ret;
}
if (!ret && rootlen > 0)
{
if (ft->id == GEANY_FILETYPES_PHP && style == SCE_HPHP_DEFAULT &&

View File

@ -281,7 +281,7 @@ GString *symbols_find_typenames_as_string(gint lang, gboolean global)
gint tag_lang;
if (global)
typedefs = tm_tags_extract(app->tm_workspace->global_tags, TM_GLOBAL_TYPE_MASK);
typedefs = app->tm_workspace->global_typename_array;
else
typedefs = app->tm_workspace->typename_array;
@ -305,8 +305,6 @@ GString *symbols_find_typenames_as_string(gint lang, gboolean global)
}
}
}
if (typedefs && global)
g_ptr_array_free(typedefs, TRUE);
return s;
}
@ -324,31 +322,7 @@ GString *symbols_find_typenames_as_string(gint lang, gboolean global)
GEANY_API_SYMBOL
const gchar *symbols_get_context_separator(gint ft_id)
{
switch (ft_id)
{
case GEANY_FILETYPES_C: /* for C++ .h headers or C structs */
case GEANY_FILETYPES_CPP:
case GEANY_FILETYPES_GLSL: /* for structs */
/*case GEANY_FILETYPES_RUBY:*/ /* not sure what to use atm*/
case GEANY_FILETYPES_PHP:
case GEANY_FILETYPES_POWERSHELL:
case GEANY_FILETYPES_RUST:
case GEANY_FILETYPES_ZEPHIR:
return "::";
/* avoid confusion with other possible separators in group/section name */
case GEANY_FILETYPES_CONF:
case GEANY_FILETYPES_REST:
return ":::";
/* no context separator */
case GEANY_FILETYPES_ASCIIDOC:
case GEANY_FILETYPES_TXT2TAGS:
return "\x03";
default:
return ".";
}
return tm_tag_context_separator(filetypes[ft_id]->lang);
}

View File

@ -126,8 +126,7 @@ static int tm_source_file_tags(const tagEntryInfo *tag)
/* Set the argument list of tag identified by its name */
static void tm_source_file_set_tag_arglist(const char *tag_name, const char *arglist)
{
guint count;
TMTag **tags, *tag;
guint i;
if (NULL == arglist ||
NULL == tag_name ||
@ -136,13 +135,16 @@ static void tm_source_file_set_tag_arglist(const char *tag_name, const char *arg
return;
}
tags = tm_tags_find(current_source_file->tags_array, tag_name, FALSE, FALSE,
&count);
if (tags != NULL && count == 1)
/* going in reverse order because the tag was added recently */
for (i = current_source_file->tags_array->len; i > 0; i--)
{
tag = tags[0];
g_free(tag->arglist);
tag->arglist = g_strdup(arglist);
TMTag *tag = (TMTag *) current_source_file->tags_array->pdata[i - 1];
if (g_strcmp0(tag->name, tag_name) == 0)
{
g_free(tag->arglist);
tag->arglist = g_strdup(arglist);
break;
}
}
}

View File

@ -17,6 +17,7 @@
#include "read.h"
#define LIBCTAGS_DEFINED
#include "tm_tag.h"
#include "tm_parser.h"
#define TAG_NEW(T) ((T) = g_slice_new0(TMTag))
@ -108,6 +109,8 @@ typedef struct
{
guint *sort_attrs;
gboolean partial;
const GPtrArray *tags_array;
gboolean first;
} TMSortOptions;
static const char *s_tag_type_names[] = {
@ -860,7 +863,7 @@ void tm_tags_remove_file_tags(TMSourceFile *source_file, GPtrArray *tags_array)
TMTag **found;
TMTag *tag = source_file->tags_array->pdata[i];
found = tm_tags_find(tags_array, tag->name, FALSE, TRUE, &tag_count);
found = tm_tags_find(tags_array, tag->name, FALSE, &tag_count);
for (j = 0; j < tag_count; j++)
{
@ -1081,85 +1084,74 @@ static gpointer binary_search(gpointer key, gpointer base, size_t nmemb,
return NULL;
}
static TMTag **tags_search(const GPtrArray *tags_array, TMTag *tag,
gboolean tags_array_sorted, TMSortOptions *sort_options)
static gint tag_search_cmp(gconstpointer ptr1, gconstpointer ptr2, gpointer user_data)
{
if (tags_array_sorted)
{ /* fast binary search on sorted tags array */
return (TMTag **) binary_search(&tag, tags_array->pdata, tags_array->len,
tm_tag_compare, sort_options);
}
else
{ /* the slow way: linear search (to make it a bit faster, search reverse assuming
* that the tag to search was added recently) */
guint i;
TMTag **t;
for (i = tags_array->len; i > 0; i--)
gint res = tm_tag_compare(ptr1, ptr2, user_data);
if (res == 0)
{
TMSortOptions *sort_options = user_data;
const GPtrArray *tags_array = sort_options->tags_array;
TMTag **tag = (TMTag **) ptr2;
/* if previous/next (depending on sort options) tag equal, we haven't
* found the first/last tag in a sequence of equal tags yet */
if (sort_options->first && ptr2 != &tags_array->pdata[0]) {
if (tm_tag_compare(ptr1, tag - 1, user_data) == 0)
return -1;
}
else if (!sort_options->first && ptr2 != &tags_array->pdata[tags_array->len-1])
{
t = (TMTag **) &tags_array->pdata[i - 1];
if (0 == tm_tag_compare(&tag, t, sort_options))
return t;
if (tm_tag_compare(ptr1, tag + 1, user_data) == 0)
return 1;
}
}
return NULL;
return res;
}
/*
Returns a pointer to the position of the first matching tag in a (sorted) tags array.
The passed array of tags should be already sorted by name for optimal performance. If
\c tags_array_sorted is set to FALSE, it may be unsorted but the lookup will be slower.
@param tags_array Tag array (may be sorted on name)
The passed array of tags must be already sorted by name (searched with binary search).
@param tags_array Tag array (sorted on name)
@param name Name of the tag to locate.
@param partial If TRUE, matches the first part of the name instead of doing exact match.
@param tags_array_sorted If TRUE, the passed \c tags_array is sorted by name so it can be
searched with binary search. Otherwise it is searched linear which is obviously slower.
@param tagCount Return location of the matched tags.
*/
TMTag **tm_tags_find(const GPtrArray *tags_array, const char *name,
gboolean partial, gboolean tags_array_sorted, guint * tagCount)
gboolean partial, guint *tagCount)
{
static TMTag *tag = NULL;
TMTag **result;
guint tagMatches=0;
TMTag *tag, **first;
TMSortOptions sort_options;
*tagCount = 0;
if ((!tags_array) || (!tags_array->len))
if (!tags_array || !tags_array->len)
return NULL;
if (NULL == tag)
tag = g_new0(TMTag, 1);
tag = g_new0(TMTag, 1);
tag->name = (char *) name;
sort_options.sort_attrs = NULL;
sort_options.partial = partial;
sort_options.tags_array = tags_array;
sort_options.first = TRUE;
first = (TMTag **)binary_search(&tag, tags_array->pdata, tags_array->len,
tag_search_cmp, &sort_options);
result = tags_search(tags_array, tag, tags_array_sorted, &sort_options);
/* There can be matches on both sides of result */
if (result)
if (first)
{
TMTag **last = (TMTag **) &tags_array->pdata[tags_array->len - 1];
TMTag **adv;
TMTag **last;
unsigned first_pos;
/* First look for any matches after result */
adv = result;
adv++;
for (; adv <= last && *adv; ++ adv)
{
if (0 != tm_tag_compare(&tag, adv, &sort_options))
break;
++tagMatches;
}
/* Now look for matches from result and below */
for (; result >= (TMTag **) tags_array->pdata; -- result)
{
if (0 != tm_tag_compare(&tag, (TMTag **) result, &sort_options))
break;
++tagMatches;
}
*tagCount=tagMatches;
++ result; /* Correct address for the last successful match */
sort_options.first = FALSE;
first_pos = first - (TMTag **)tags_array->pdata;
/* search between the first element and end */
last = (TMTag **)binary_search(&tag, first, tags_array->len - first_pos,
tag_search_cmp, &sort_options);
*tagCount = last - first + 1;
}
return (TMTag **) result;
g_free(tag);
return (TMTag **) first;
}
/* Returns TMTag which "own" given line
@ -1190,17 +1182,48 @@ tm_get_current_tag (GPtrArray * file_tags, const gulong line, const TMTagType ta
return matching_tag;
}
#if 0
/* Returns TMTag to function or method which "own" given line
@param line Current line in edited file.
@param file_tags A GPtrArray of edited file TMTag pointers.
@return TMTag pointers to owner function. */
static const TMTag *
tm_get_current_function (GPtrArray * file_tags, const gulong line)
const gchar *tm_tag_context_separator(langType lang)
{
return tm_get_current_tag (file_tags, line, tm_tag_function_t | tm_tag_method_t);
switch (lang)
{
case TM_PARSER_C: /* for C++ .h headers or C structs */
case TM_PARSER_CPP:
case TM_PARSER_GLSL: /* for structs */
/*case GEANY_FILETYPES_RUBY:*/ /* not sure what to use atm*/
case TM_PARSER_PHP:
case TM_PARSER_POWERSHELL:
case TM_PARSER_RUST:
case TM_PARSER_ZEPHIR:
return "::";
/* avoid confusion with other possible separators in group/section name */
case TM_PARSER_CONF:
case TM_PARSER_REST:
return ":::";
/* no context separator */
case TM_PARSER_ASCIIDOC:
case TM_PARSER_TXT2TAGS:
return "\x03";
default:
return ".";
}
}
gboolean tm_tag_is_anon(const TMTag *tag)
{
guint i;
char dummy;
if (tag->lang == TM_PARSER_C || tag->lang == TM_PARSER_CPP)
return sscanf(tag->name, "anon_%*[a-z]_%u%c", &i, &dummy) == 1;
else if (tag->lang == TM_PARSER_FORTRAN || tag->lang == TM_PARSER_F77)
return sscanf(tag->name, "Structure#%u%c", &i, &dummy) == 1 ||
sscanf(tag->name, "Interface#%u%c", &i, &dummy) == 1 ||
sscanf(tag->name, "Enum#%u%c", &i, &dummy) == 1;
return FALSE;
}
#endif
#ifdef TM_DEBUG /* various debugging functions */
@ -1343,11 +1366,12 @@ void tm_tags_array_print(GPtrArray *tags, FILE *fp)
*/
gint tm_tag_scope_depth(const TMTag *t)
{
const gchar *context_sep = tm_tag_context_separator(t->lang);
gint depth;
char *s;
if(!(t && t->scope))
return 0;
for (s = t->scope, depth = 0; s; s = strstr(s, "::"))
for (s = t->scope, depth = 0; s; s = strstr(s, context_sep))
{
++ depth;
++ s;

View File

@ -179,7 +179,7 @@ gboolean tm_tags_prune(GPtrArray *tags_array);
gboolean tm_tags_dedup(GPtrArray *tags_array, TMTagAttrType *sort_attributes, gboolean unref_duplicates);
TMTag **tm_tags_find(const GPtrArray *tags_array, const char *name,
gboolean partial, gboolean tags_array_sorted, guint * tagCount);
gboolean partial, guint * tagCount);
void tm_tags_array_free(GPtrArray *tags_array, gboolean free_all);
@ -191,6 +191,10 @@ TMTag *tm_tag_ref(TMTag *tag);
gboolean tm_tags_equal(const TMTag *a, const TMTag *b);
const gchar *tm_tag_context_separator(langType lang);
gboolean tm_tag_is_anon(const TMTag *tag);
#ifdef TM_DEBUG /* various debugging functions */
const char *tm_tag_type_name(const TMTag *tag);

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ typedef struct
GPtrArray *tags_array; /**< Sorted tags from all source files
(just pointers to source file tags, the tag objects are owned by the source files) */
GPtrArray *typename_array; /* Typename tags for syntax highlighting (pointers owned by source files) */
GPtrArray *global_typename_array; /* Like above for global tags */
} TMWorkspace;
@ -54,17 +55,14 @@ gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode);
gboolean tm_workspace_create_global_tags(const char *pre_process, const char **includes,
int includes_count, const char *tags_file, int lang);
const GPtrArray *tm_workspace_find(const char *name, TMTagType type, TMTagAttrType *attrs,
gboolean partial, langType lang);
GPtrArray *tm_workspace_find(const char *name, const char *scope, TMTagType type,
TMTagAttrType *attrs, langType lang);
const GPtrArray *
tm_workspace_find_scoped (const char *name, const char *scope, TMTagType type,
TMTagAttrType *attrs, gboolean partial, langType lang, gboolean global_search);
GPtrArray *tm_workspace_find_prefix(const char *prefix, langType lang, guint max_num);
GPtrArray *tm_workspace_find_scope_members (TMSourceFile *source_file, const char *name,
gboolean function, gboolean member, const gchar *current_scope);
const GPtrArray *tm_workspace_find_scope_members(const GPtrArray *file_tags,
const char *scope_name,
gboolean find_global,
gboolean no_definitions);
void tm_workspace_add_source_file_noupdate(TMSourceFile *source_file);