From 4956b05d1e9052861deb3608673da25741bc6013 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Thu, 23 Mar 2017 21:46:23 +0100 Subject: [PATCH 1/9] utils: add functions to process file name list 1) utils_strv_find_common_prefix Locates the common prefix. 2) utils_strv_find_lcs Finds the longest common substring. 3) utils_strv_shorten_file_list Transforms the file list by removing the common prefix and ellipsizing the longest common substring. This is intended to be used for fixing #1069. Although only 3 will be used immediately, I separated the functionality, so that the other two function can be used on their own. --- src/utils.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.h | 2 + 2 files changed, 194 insertions(+) diff --git a/src/utils.c b/src/utils.c index 0a03a68f0e..126a649ef7 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2044,6 +2044,198 @@ gchar **utils_strv_join(gchar **first, gchar **second) return strv; } +/* * Returns the common prefix in a list of strings. + * + * The size of the list may be given explicitely automatically determined if passed a GStrv. + * + * @param strv The list of strings to process. + * @param num The number of strings contained in @a strv. Can be 0 if @a strv is a @c GStrv + * + * @return The common prefix that is part of all strings (maybe empty), or NULL if an empty list + * was passed in. + */ +static gchar *utils_strv_find_common_prefix(gchar **strv, size_t num) +{ + gchar *prefix, **ptr; + + if (!NZV(strv)) + return NULL; + + if (num == 0) + num = g_strv_length(strv); + + prefix = g_strdup(strv[0]); + + for (gint i = 0; prefix[i]; i++) + { + foreach_strv(ptr, &strv[1]) + for (gint j = 1; j < num; j++) + { + gchar *s = strv[j]; + if (s[i] != prefix[i]) + { + /* terminate prefix on first mismatch and return */ + prefix[i] = '\0'; + break; + } + } + if (prefix[i] == '\0') + break; + } + return prefix; +} + +/* * Returns the common prefix in a list of strings. + * + * The size of the list may be given explicitely automatically determined if passed a GStrv. + * + * @param strv The list of strings to process. + * @param num The number of strings contained in @a strv. Can be 0 if @a strv is a @c GStrv + * + * @return The common prefix that is part of all strings. + */ +gchar *utils_strv_find_lcs(gchar **strv, size_t num) +{ + gchar *first, *other, *_sub, *sub; + gsize n_chars; + gsize len; + gsize max = 0; + char *lcs; + gint found; + + if (strv == NULL) + return NULL; + + first = strv[0]; + len = strlen(first); + + if (num == 0) + num = g_strv_length(strv); + + /* sub is the working area where substrings from first are copied to */ + sub = g_malloc(len+1); + lcs = g_strdup(""); + foreach_str(_sub, first) + { + gsize chars_left = len - (_sub - first); + /* No point in continuing if the remainder is too short */ + if (max > chars_left) + break; + for (n_chars = 1; n_chars <= chars_left; n_chars++) + { + /* strlcpy() ftw! */ + memcpy(sub, _sub, n_chars); + sub[n_chars] = '\0'; + found = 1; + for (gint i = 1; i < num; i++) + { + if (strstr(strv[i], sub) == NULL) + break; + found++; + } + if (found == num && n_chars > max) + { + max = n_chars; + SETPTR(lcs, g_strdup(sub)); + } + } + } + g_free(sub); + + return lcs; +} + + +/** Transform file names in a list to be shorter. + * + * This function takes a list of file names (porbably with absolute paths), and + * transforms the paths such that they are short but still unique. This is intended + * for dialogs which present the file list to the user, where the base name may result + * in duplicates (showing the full path might be inappropriate). + * + * The algorthm strips the common prefix (e-g. the user's home directory) and + * replaces the longest common substring with "...". + * + * @param file_names @arraylen{num} The list of strings to process. + * @param num The number of strings contained in @a strv. Can be 0 if @a strv is a @c GStrv + * @return @transfer{full} A newly-allocated NULL-terminated array of transformed paths strings. Use @c g_strfreev() to free it. + * + * @since 1.31 (API 232 + */ +GEANY_API_SYMBOL +gchar **utils_strv_shorten_file_list(gchar **file_names, size_t num) +{ + gint i, j; + gchar *prefix, *substring, *name, *sep, **s; + TMTag *tmtag; + gchar **names; + gsize len; + + /* The return value shall have exactly the same size as the input. If the input is a + * GStrv (last element is NULL), the output will follow suit. */ + if (!num) + num = g_strv_length(file_names); + /* Always include a terminating NULL, enables easy freeing with g_strfreev() */ + names = g_new(gchar *, num + 1); + names[num] = 0; + + prefix = utils_strv_find_common_prefix(file_names, num); + /* First: determine the common prefix, that will be stripped. + * Don't strip single-letter prefixes, such as '/' */ + len = 0; + if (NZV(prefix) && prefix[1]) + { + /* Only strip directory components, include trailing '/' */ + sep = strrchr(prefix, G_DIR_SEPARATOR); + if (sep) + len = sep - prefix + 1; + } + + for (i = 0; i < num; i++) + names[i] = g_strdup(file_names[i] + len); + + /* Second: determine the longest common substring, that will be ellipsized */ + substring = utils_strv_find_lcs(names, num); + if (NZV(substring)) + { + /* Only ellipsize directory components. Directory delimiters ought + * to be part of the substring. If it doesn't contain at least two + * separators, then there isn't even a single directory to ellipsize + * (also take care to not ellipsize the base file name). */ + gchar *start; + sep = strchr(substring, G_DIR_SEPARATOR); + + if (sep) + { + len = 0; + start = sep + 1; + sep = strrchr(start, G_DIR_SEPARATOR); + if (sep) + { + *sep = '\0'; + len = strlen(start); + } + /* Don't bother for tiny substrings. */ + if (len >= 5) + { + for (i = 0; i < num; i++) + { + gchar *s = strstr(names[i], start); + gchar *rem = s + len; /* +1 skips over the leading '/' */ + gsize copy_n = strlen(rem) + 1; /* include NUL */ + memcpy(s, "...", 3); /* Maybe replace with unicode's "…" ? */ + memmove(s+3, rem, copy_n); + } + } + } + } + + g_free(substring); + g_free(prefix); + + return names; +} + /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint, * obviously g_date_set_parse() uses some magic. diff --git a/src/utils.h b/src/utils.h index d2596d8731..154e508512 100644 --- a/src/utils.h +++ b/src/utils.h @@ -301,6 +301,8 @@ gchar **utils_strv_new(const gchar *first, ...) G_GNUC_NULL_TERMINATED; gchar **utils_strv_join(gchar **first, gchar **second) G_GNUC_WARN_UNUSED_RESULT; +gchar **utils_strv_shorten_file_list(gchar **file_names, size_t num); + GSList *utils_get_config_files(const gchar *subdir); gchar *utils_get_help_url(const gchar *suffix); From b116a66862eb89b0c12833eb9bc6ecafcb6c7284 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Thu, 23 Mar 2017 21:58:43 +0100 Subject: [PATCH 2/9] symbols: provide a bit more path information in the goto-symbol popup. From #1069: > At the moment if symbols of the same name are defined in identically named > files, it's hard to distinguish which file is which because there's no path > in the popup. > The popup should show part of the path until a directory where the paths > differ so it's possible to distinguish the different files. At the same time > there should probably be some top limit for the length of the paths as they > can make the popup too wide. This addresses the above by showing more of the file's paths but still try to make it as short as possible. The file list is processed by the new utils_strv_shorten_file_list(), as a result the popup will list files with the common prefix stripped and the longest common sub-path ellipsized. As a result, the file list shows enough of the path to make them unique but still is still very short and doesn't make the dialog too wide. Fixes #1069. --- src/symbols.c | 13 +++++++++++-- src/utils.c | 9 +++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/symbols.c b/src/symbols.c index 0133c28db3..87c0496b59 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -1933,15 +1933,23 @@ static void show_goto_popup(GeanyDocument *doc, GPtrArray *tags, gboolean have_b GdkEventButton *button_event = NULL; TMTag *tmtag; guint i; - + gchar **short_names, **file_names, **p; menu = gtk_menu_new(); + /* If popup would show multiple files presend a smart file list that allows + * to easily distinguish the files while avoiding the file paths in their entirety */ + file_names = g_new(gchar *, tags->len); + foreach_ptr_array(tmtag, i, tags) + file_names[i] = tmtag->file->file_name; + short_names = utils_strv_shorten_file_list(file_names, tags->len); + g_free(file_names); + foreach_ptr_array(tmtag, i, tags) { GtkWidget *item; GtkWidget *label; GtkWidget *image; - gchar *fname = g_path_get_basename(tmtag->file->file_name); + gchar *fname = short_names[i]; gchar *text; if (! first && have_best) @@ -1964,6 +1972,7 @@ static void show_goto_popup(GeanyDocument *doc, GPtrArray *tags, gboolean have_b g_free(text); g_free(fname); } + g_free(short_names); gtk_widget_show_all(menu); diff --git a/src/utils.c b/src/utils.c index 126a649ef7..455fad1c6d 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2146,7 +2146,7 @@ gchar *utils_strv_find_lcs(gchar **strv, size_t num) } -/** Transform file names in a list to be shorter. +/* * Transform file names in a list to be shorter. * * This function takes a list of file names (porbably with absolute paths), and * transforms the paths such that they are short but still unique. This is intended @@ -2156,13 +2156,10 @@ gchar *utils_strv_find_lcs(gchar **strv, size_t num) * The algorthm strips the common prefix (e-g. the user's home directory) and * replaces the longest common substring with "...". * - * @param file_names @arraylen{num} The list of strings to process. + * @param file_names The list of strings to process. * @param num The number of strings contained in @a strv. Can be 0 if @a strv is a @c GStrv - * @return @transfer{full} A newly-allocated NULL-terminated array of transformed paths strings. Use @c g_strfreev() to free it. - * - * @since 1.31 (API 232 + * @return A newly-allocated NULL-terminated array of transformed paths strings. Use @c g_strfreev() to free it. */ -GEANY_API_SYMBOL gchar **utils_strv_shorten_file_list(gchar **file_names, size_t num) { gint i, j; From 3512f6cc7430cbc8c52b0b474e5b72d58ed75863 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Thu, 23 Mar 2017 22:12:39 +0100 Subject: [PATCH 3/9] gtkdoc: add support for array annotions We can now use @array and @arraylen{param} annotations for arrays that will make it to the generated gtkdoc header. g-ir-scanner cannot properly parse 'gchar **' parameters without this. --- doc/Doxyfile.in | 2 ++ scripts/gen-api-gtkdoc.py | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 39463a2f65..db0ab8c191 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -262,6 +262,8 @@ ALIASES += "optional=\noop \xmlonly notified\endxmlonly" ALIASES += "cbdata=\noop \xmlonly \endxmlonly" ALIASES += "cbfree=\noop \xmlonly \endxmlonly" +ALIASES += "array=\noop \xmlonly \endxmlonly" +ALIASES += "arraylen{1}=\noop \xmlonly length=\1\endxmlonly" # This tag can be used to specify a number of word-keyword mappings (TCL only). diff --git a/scripts/gen-api-gtkdoc.py b/scripts/gen-api-gtkdoc.py index ec1cd74874..f04271b233 100755 --- a/scripts/gen-api-gtkdoc.py +++ b/scripts/gen-api-gtkdoc.py @@ -75,11 +75,14 @@ def cb(self, type, str): "geany:closure", "geany:destroy"): self.annot.append(type.split(":")[1]) - elif type in ("geany:transfer", + elif type in ("geany:array", + "geany:transfer", "geany:element-type", "geany:scope"): type = type.split(":")[1] - self.annot.append("%s %s" % (type, str)) + if len(str): + str = " " + str + self.annot.append("%s%s" % (type, str)) elif (type == "see"): return "See " + str elif type in ("a", "c") and str in ("NULL", "TRUE", "FALSE"): From c05837055b0533504b85966852b6f26551616e30 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Thu, 23 Mar 2017 22:16:11 +0100 Subject: [PATCH 4/9] api: export new utils_strv_shorten_file_list() function Since I based the algorithm of the above function on code in one of my python plugins, I would like to remove the implementation in my plugin and call Geany's function. --- src/utils.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/utils.c b/src/utils.c index 455fad1c6d..83d23ed1cf 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2146,7 +2146,7 @@ gchar *utils_strv_find_lcs(gchar **strv, size_t num) } -/* * Transform file names in a list to be shorter. +/** Transform file names in a list to be shorter. * * This function takes a list of file names (porbably with absolute paths), and * transforms the paths such that they are short but still unique. This is intended @@ -2156,10 +2156,13 @@ gchar *utils_strv_find_lcs(gchar **strv, size_t num) * The algorthm strips the common prefix (e-g. the user's home directory) and * replaces the longest common substring with "...". * - * @param file_names The list of strings to process. + * @param file_names @arraylen{num} The list of strings to process. * @param num The number of strings contained in @a strv. Can be 0 if @a strv is a @c GStrv - * @return A newly-allocated NULL-terminated array of transformed paths strings. Use @c g_strfreev() to free it. + * @return @transfer{full} A newly-allocated NULL-terminated array of transformed paths strings. Use @c g_strfreev() to free it. + * + * @since 1.34 (API 239) */ +GEANY_API_SYMBOL gchar **utils_strv_shorten_file_list(gchar **file_names, size_t num) { gint i, j; From 7fdd360b85fe3a920f9510a18e32afa6ee83fe22 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Fri, 24 Mar 2017 07:24:48 +0100 Subject: [PATCH 5/9] Fix one oops and a couple of mistakes in comments, found by review. --- src/utils.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/utils.c b/src/utils.c index 83d23ed1cf..e5b644e5ef 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2046,10 +2046,10 @@ gchar **utils_strv_join(gchar **first, gchar **second) /* * Returns the common prefix in a list of strings. * - * The size of the list may be given explicitely automatically determined if passed a GStrv. + * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv). * * @param strv The list of strings to process. - * @param num The number of strings contained in @a strv. Can be 0 if @a strv is a @c GStrv + * @param num The number of strings contained in @a strv. Can be 0 if it's terminated by @c NULL. * * @return The common prefix that is part of all strings (maybe empty), or NULL if an empty list * was passed in. @@ -2068,7 +2068,6 @@ static gchar *utils_strv_find_common_prefix(gchar **strv, size_t num) for (gint i = 0; prefix[i]; i++) { - foreach_strv(ptr, &strv[1]) for (gint j = 1; j < num; j++) { gchar *s = strv[j]; @@ -2085,12 +2084,12 @@ static gchar *utils_strv_find_common_prefix(gchar **strv, size_t num) return prefix; } -/* * Returns the common prefix in a list of strings. +/* * Returns the longest common substring in a list of strings. * - * The size of the list may be given explicitely automatically determined if passed a GStrv. + * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv). * * @param strv The list of strings to process. - * @param num The number of strings contained in @a strv. Can be 0 if @a strv is a @c GStrv + * @param num The number of strings contained in @a strv. Can be 0 if it's terminated by @c NULL. * * @return The common prefix that is part of all strings. */ @@ -2154,11 +2153,12 @@ gchar *utils_strv_find_lcs(gchar **strv, size_t num) * in duplicates (showing the full path might be inappropriate). * * The algorthm strips the common prefix (e-g. the user's home directory) and - * replaces the longest common substring with "...". + * replaces the longest common substring with an ellipsis ("..."). * * @param file_names @arraylen{num} The list of strings to process. - * @param num The number of strings contained in @a strv. Can be 0 if @a strv is a @c GStrv - * @return @transfer{full} A newly-allocated NULL-terminated array of transformed paths strings. Use @c g_strfreev() to free it. + * @param num The number of strings contained in @a file_names. Can be 0 if it's terminated by @c NULL. + * @return @transfer{full} A newly-allocated array of transformed paths strings, terminated by + @c NULL. Use @c g_strfreev() to free it. * * @since 1.34 (API 239) */ From fb9673eb61c44baba6a94f7ad3ef429ffa021c73 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Thu, 30 Mar 2017 08:02:29 +0200 Subject: [PATCH 6/9] api: pass gtkdoc annotation parameter as-is The array annotation has many possible parameters, this avoids having a Doxygen command for each one. Luckily you can define Doxygen commands multiple times with different a number of parameters each. --- doc/Doxyfile.in | 2 +- src/utils.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index db0ab8c191..3a2d4d6be8 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -263,7 +263,7 @@ ALIASES += "cb=\noop \xmlonly not ALIASES += "cbdata=\noop \xmlonly \endxmlonly" ALIASES += "cbfree=\noop \xmlonly \endxmlonly" ALIASES += "array=\noop \xmlonly \endxmlonly" -ALIASES += "arraylen{1}=\noop \xmlonly length=\1\endxmlonly" +ALIASES += "array{1}=\noop \xmlonly \1\endxmlonly" # This tag can be used to specify a number of word-keyword mappings (TCL only). diff --git a/src/utils.c b/src/utils.c index e5b644e5ef..3259bd824d 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2155,7 +2155,7 @@ gchar *utils_strv_find_lcs(gchar **strv, size_t num) * The algorthm strips the common prefix (e-g. the user's home directory) and * replaces the longest common substring with an ellipsis ("..."). * - * @param file_names @arraylen{num} The list of strings to process. + * @param file_names @array{length=num} The list of strings to process. * @param num The number of strings contained in @a file_names. Can be 0 if it's terminated by @c NULL. * @return @transfer{full} A newly-allocated array of transformed paths strings, terminated by @c NULL. Use @c g_strfreev() to free it. From e0a2c6277ae739de6af6c1df311fc9979b54915a Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Thu, 15 Mar 2018 17:15:59 +0100 Subject: [PATCH 7/9] Refactoring and review comments - Fix lots of compiler warnings - Fix a bug where a long base name would prevent ellipsizing the longest common substring - rewrite utils_strv_shorten_file_list to be more clear (hopefully) - use g_strlcpy - optimize case where the longest common substring need not be searched for --- src/utils.c | 115 ++++++++++++++++++++++++++++------------------------ 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/src/utils.c b/src/utils.c index 3259bd824d..d51c10ae5e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2054,9 +2054,9 @@ gchar **utils_strv_join(gchar **first, gchar **second) * @return The common prefix that is part of all strings (maybe empty), or NULL if an empty list * was passed in. */ -static gchar *utils_strv_find_common_prefix(gchar **strv, size_t num) +static gchar *utils_strv_find_common_prefix(gchar **strv, gsize num) { - gchar *prefix, **ptr; + gchar *prefix; if (!NZV(strv)) return NULL; @@ -2068,7 +2068,7 @@ static gchar *utils_strv_find_common_prefix(gchar **strv, size_t num) for (gint i = 0; prefix[i]; i++) { - for (gint j = 1; j < num; j++) + for (gsize j = 1; j < num; j++) { gchar *s = strv[j]; if (s[i] != prefix[i]) @@ -2093,14 +2093,14 @@ static gchar *utils_strv_find_common_prefix(gchar **strv, size_t num) * * @return The common prefix that is part of all strings. */ -gchar *utils_strv_find_lcs(gchar **strv, size_t num) +static gchar *utils_strv_find_lcs(gchar **strv, gsize num) { - gchar *first, *other, *_sub, *sub; + gchar *first, *_sub, *sub; gsize n_chars; gsize len; gsize max = 0; char *lcs; - gint found; + gsize found; if (strv == NULL) return NULL; @@ -2122,11 +2122,9 @@ gchar *utils_strv_find_lcs(gchar **strv, size_t num) break; for (n_chars = 1; n_chars <= chars_left; n_chars++) { - /* strlcpy() ftw! */ - memcpy(sub, _sub, n_chars); - sub[n_chars] = '\0'; + g_strlcpy(sub, _sub, n_chars+1); found = 1; - for (gint i = 1; i < num; i++) + for (gsize i = 1; i < num; i++) { if (strstr(strv[i], sub) == NULL) break; @@ -2147,7 +2145,7 @@ gchar *utils_strv_find_lcs(gchar **strv, size_t num) /** Transform file names in a list to be shorter. * - * This function takes a list of file names (porbably with absolute paths), and + * This function takes a list of file names (probably with absolute paths), and * transforms the paths such that they are short but still unique. This is intended * for dialogs which present the file list to the user, where the base name may result * in duplicates (showing the full path might be inappropriate). @@ -2163,73 +2161,86 @@ gchar *utils_strv_find_lcs(gchar **strv, size_t num) * @since 1.34 (API 239) */ GEANY_API_SYMBOL -gchar **utils_strv_shorten_file_list(gchar **file_names, size_t num) +gchar **utils_strv_shorten_file_list(gchar **file_names, gsize num) { - gint i, j; - gchar *prefix, *substring, *name, *sep, **s; - TMTag *tmtag; + gsize i; + gchar *prefix, *substring, *lcs; + gchar *start, *end; gchar **names; - gsize len; + gsize prefix_len, lcs_len; /* The return value shall have exactly the same size as the input. If the input is a * GStrv (last element is NULL), the output will follow suit. */ if (!num) num = g_strv_length(file_names); /* Always include a terminating NULL, enables easy freeing with g_strfreev() */ - names = g_new(gchar *, num + 1); - names[num] = 0; + names = g_new0(gchar *, num + 1); prefix = utils_strv_find_common_prefix(file_names, num); /* First: determine the common prefix, that will be stripped. * Don't strip single-letter prefixes, such as '/' */ - len = 0; + prefix_len = 0; if (NZV(prefix) && prefix[1]) { /* Only strip directory components, include trailing '/' */ - sep = strrchr(prefix, G_DIR_SEPARATOR); - if (sep) - len = sep - prefix + 1; + start = strrchr(prefix, G_DIR_SEPARATOR); + if (start) + prefix_len = start - prefix + 1; } - for (i = 0; i < num; i++) - names[i] = g_strdup(file_names[i] + len); + /* Second: determine the longest common substring (lcs), that will be ellipsized. Look + * only at the directory parts since we don't want the file name to be detected as the lcs. + * Also, the first component cannot be common (since it would be part of the common prefix), + * so ignore that as well. + * Surround with dir separators so that we can easily determine the boundaries for ellipsizing. */ + for (i = 0; i < num; i++) { + start = strchr(file_names[i] + prefix_len, G_DIR_SEPARATOR); + end = start ? strrchr(start+1, G_DIR_SEPARATOR) : NULL; + /* Breaking out early will also skip looking for lcs (wouldn't succeed anyway). */ + if (!start || !end) + break; + names[i] = g_strndup(start, end-start+1); + } - /* Second: determine the longest common substring, that will be ellipsized */ - substring = utils_strv_find_lcs(names, num); + lcs_len = 0; + substring = (i == num) ? utils_strv_find_lcs(names, num) : NULL; if (NZV(substring)) { - /* Only ellipsize directory components. Directory delimiters ought - * to be part of the substring. If it doesn't contain at least two - * separators, then there isn't even a single directory to ellipsize - * (also take care to not ellipsize the base file name). */ - gchar *start; - sep = strchr(substring, G_DIR_SEPARATOR); - - if (sep) + /* Strip leading component. */ + start = strchr(substring, G_DIR_SEPARATOR); + if (start) { - len = 0; - start = sep + 1; - sep = strrchr(start, G_DIR_SEPARATOR); - if (sep) + lcs = start + 1; + /* Strip trailing components (perhaps empty). */ + end = strrchr(lcs, G_DIR_SEPARATOR); + if (end) { - *sep = '\0'; - len = strlen(start); - } - /* Don't bother for tiny substrings. */ - if (len >= 5) - { - for (i = 0; i < num; i++) - { - gchar *s = strstr(names[i], start); - gchar *rem = s + len; /* +1 skips over the leading '/' */ - gsize copy_n = strlen(rem) + 1; /* include NUL */ - memcpy(s, "...", 3); /* Maybe replace with unicode's "…" ? */ - memmove(s+3, rem, copy_n); - } + *end = '\0'; + lcs_len = strlen(lcs); + /* Don't bother for tiny common parts (which are often just "." or "/"). */ + if (lcs_len < 5) + lcs_len = 0; } } } + /* Finally build the shortened list of unique file names */ + for (i = 0; i < num; i++) + { + start = file_names[i] + prefix_len; + if (lcs_len == 0) + { /* no lcs, copy without prefix */ + SETPTR(names[i], g_strdup(start)); + } + else + { + const gchar *lcs_start = strstr(start, lcs); + const gchar *lcs_end = lcs_start + lcs_len; + /* Maybe replace with unicode's "…" ? */ + SETPTR(names[i], g_strdup_printf("%.*s...%s", (int)(lcs_start - start), start, lcs_end)); + } + } + g_free(substring); g_free(prefix); From 724e7886dec8da92cc3e7b50c861499a6b6932eb Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Sun, 18 Nov 2018 13:35:00 +0100 Subject: [PATCH 8/9] Changes for review comments - Notably the utils_strv_{find_common_prefix,find_lcs,shorten_file_list} now take -1 for num to mean to compute the array length. - utils_strv_find_common_prefix implementation simplified. - if num == 0 is passed to the above functions the passed strv is not dereferenced (so could be NULL). --- src/symbols.c | 4 ++-- src/utils.c | 47 ++++++++++++++++++++++------------------------- src/utils.h | 2 +- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/symbols.c b/src/symbols.c index 87c0496b59..6ce1bb5a43 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -1933,10 +1933,10 @@ static void show_goto_popup(GeanyDocument *doc, GPtrArray *tags, gboolean have_b GdkEventButton *button_event = NULL; TMTag *tmtag; guint i; - gchar **short_names, **file_names, **p; + gchar **short_names, **file_names; menu = gtk_menu_new(); - /* If popup would show multiple files presend a smart file list that allows + /* If popup would show multiple files present a smart file list that allows * to easily distinguish the files while avoiding the file paths in their entirety */ file_names = g_new(gchar *, tags->len); foreach_ptr_array(tmtag, i, tags) diff --git a/src/utils.c b/src/utils.c index d51c10ae5e..89f520e362 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2049,51 +2049,45 @@ gchar **utils_strv_join(gchar **first, gchar **second) * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv). * * @param strv The list of strings to process. - * @param num The number of strings contained in @a strv. Can be 0 if it's terminated by @c NULL. + * @param num The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL. * * @return The common prefix that is part of all strings (maybe empty), or NULL if an empty list * was passed in. */ -static gchar *utils_strv_find_common_prefix(gchar **strv, gsize num) +static gchar *utils_strv_find_common_prefix(gchar **strv, gssize num) { - gchar *prefix; - if (!NZV(strv)) return NULL; - if (num == 0) + if (num == -1) num = g_strv_length(strv); - prefix = g_strdup(strv[0]); - - for (gint i = 0; prefix[i]; i++) + for (gsize i = 0; strv[0][i]; i++) { for (gsize j = 1; j < num; j++) { - gchar *s = strv[j]; - if (s[i] != prefix[i]) + if (strv[j][i] != strv[0][i]) { - /* terminate prefix on first mismatch and return */ - prefix[i] = '\0'; - break; + /* return prefix on first mismatch */ + return g_strndup(strv[0], i); } } - if (prefix[i] == '\0') - break; } - return prefix; + + return g_strdup(strv[0]); } + /* * Returns the longest common substring in a list of strings. * * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv). * * @param strv The list of strings to process. - * @param num The number of strings contained in @a strv. Can be 0 if it's terminated by @c NULL. + * @param num The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL. * * @return The common prefix that is part of all strings. */ -static gchar *utils_strv_find_lcs(gchar **strv, gsize num) +static gchar *utils_strv_find_lcs(gchar **strv, gssize num) { gchar *first, *_sub, *sub; gsize n_chars; @@ -2102,15 +2096,15 @@ static gchar *utils_strv_find_lcs(gchar **strv, gsize num) char *lcs; gsize found; - if (strv == NULL) + if (num == -1) + num = g_strv_length(strv); + + if (num < 1) return NULL; first = strv[0]; len = strlen(first); - if (num == 0) - num = g_strv_length(strv); - /* sub is the working area where substrings from first are copied to */ sub = g_malloc(len+1); lcs = g_strdup(""); @@ -2154,14 +2148,14 @@ static gchar *utils_strv_find_lcs(gchar **strv, gsize num) * replaces the longest common substring with an ellipsis ("..."). * * @param file_names @array{length=num} The list of strings to process. - * @param num The number of strings contained in @a file_names. Can be 0 if it's terminated by @c NULL. + * @param num The number of strings contained in @a file_names. Can be -1 if it's terminated by @c NULL. * @return @transfer{full} A newly-allocated array of transformed paths strings, terminated by @c NULL. Use @c g_strfreev() to free it. * * @since 1.34 (API 239) */ GEANY_API_SYMBOL -gchar **utils_strv_shorten_file_list(gchar **file_names, gsize num) +gchar **utils_strv_shorten_file_list(gchar **file_names, gssize num) { gsize i; gchar *prefix, *substring, *lcs; @@ -2169,9 +2163,12 @@ gchar **utils_strv_shorten_file_list(gchar **file_names, gsize num) gchar **names; gsize prefix_len, lcs_len; + /* We don't dereference file_names if num == 0. */ + g_return_val_if_fail(num == 0 || file_names != NULL, NULL); + /* The return value shall have exactly the same size as the input. If the input is a * GStrv (last element is NULL), the output will follow suit. */ - if (!num) + if (num == -1) num = g_strv_length(file_names); /* Always include a terminating NULL, enables easy freeing with g_strfreev() */ names = g_new0(gchar *, num + 1); diff --git a/src/utils.h b/src/utils.h index 154e508512..4dbfdb9722 100644 --- a/src/utils.h +++ b/src/utils.h @@ -301,7 +301,7 @@ gchar **utils_strv_new(const gchar *first, ...) G_GNUC_NULL_TERMINATED; gchar **utils_strv_join(gchar **first, gchar **second) G_GNUC_WARN_UNUSED_RESULT; -gchar **utils_strv_shorten_file_list(gchar **file_names, size_t num); +gchar **utils_strv_shorten_file_list(gchar **file_names, gssize num); GSList *utils_get_config_files(const gchar *subdir); From 8f16685d9e29fc260eb43c6eb61bbf6ee54ee4df Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sun, 2 Dec 2018 10:41:09 +0100 Subject: [PATCH 9/9] Fix a few signed vs unsigned comparisons --- src/utils.c | 33 +++++++++++++++++---------------- src/utils.h | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/utils.c b/src/utils.c index 89f520e362..e88dddaf92 100644 --- a/src/utils.c +++ b/src/utils.c @@ -2049,18 +2049,19 @@ gchar **utils_strv_join(gchar **first, gchar **second) * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv). * * @param strv The list of strings to process. - * @param num The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL. + * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL. * * @return The common prefix that is part of all strings (maybe empty), or NULL if an empty list * was passed in. */ -static gchar *utils_strv_find_common_prefix(gchar **strv, gssize num) +static gchar *utils_strv_find_common_prefix(gchar **strv, gssize strv_len) { + gsize num; + if (!NZV(strv)) return NULL; - if (num == -1) - num = g_strv_length(strv); + num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len; for (gsize i = 0; strv[0][i]; i++) { @@ -2083,22 +2084,21 @@ static gchar *utils_strv_find_common_prefix(gchar **strv, gssize num) * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv). * * @param strv The list of strings to process. - * @param num The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL. + * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL. * * @return The common prefix that is part of all strings. */ -static gchar *utils_strv_find_lcs(gchar **strv, gssize num) +static gchar *utils_strv_find_lcs(gchar **strv, gssize strv_len) { gchar *first, *_sub, *sub; + gsize num; gsize n_chars; gsize len; gsize max = 0; char *lcs; gsize found; - if (num == -1) - num = g_strv_length(strv); - + num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len; if (num < 1) return NULL; @@ -2147,29 +2147,30 @@ static gchar *utils_strv_find_lcs(gchar **strv, gssize num) * The algorthm strips the common prefix (e-g. the user's home directory) and * replaces the longest common substring with an ellipsis ("..."). * - * @param file_names @array{length=num} The list of strings to process. - * @param num The number of strings contained in @a file_names. Can be -1 if it's terminated by @c NULL. + * @param file_names @array{length=file_names_len} The list of strings to process. + * @param file_names_len The number of strings contained in @a file_names. Can be -1 if it's + * terminated by @c NULL. * @return @transfer{full} A newly-allocated array of transformed paths strings, terminated by @c NULL. Use @c g_strfreev() to free it. * * @since 1.34 (API 239) */ GEANY_API_SYMBOL -gchar **utils_strv_shorten_file_list(gchar **file_names, gssize num) +gchar **utils_strv_shorten_file_list(gchar **file_names, gssize file_names_len) { + gsize num; gsize i; gchar *prefix, *substring, *lcs; gchar *start, *end; gchar **names; gsize prefix_len, lcs_len; - /* We don't dereference file_names if num == 0. */ - g_return_val_if_fail(num == 0 || file_names != NULL, NULL); + /* We don't dereference file_names if file_names_len == 0. */ + g_return_val_if_fail(file_names_len == 0 || file_names != NULL, NULL); /* The return value shall have exactly the same size as the input. If the input is a * GStrv (last element is NULL), the output will follow suit. */ - if (num == -1) - num = g_strv_length(file_names); + num = (file_names_len == -1) ? g_strv_length(file_names) : (gsize) file_names_len; /* Always include a terminating NULL, enables easy freeing with g_strfreev() */ names = g_new0(gchar *, num + 1); diff --git a/src/utils.h b/src/utils.h index 4dbfdb9722..06454a4033 100644 --- a/src/utils.h +++ b/src/utils.h @@ -301,7 +301,7 @@ gchar **utils_strv_new(const gchar *first, ...) G_GNUC_NULL_TERMINATED; gchar **utils_strv_join(gchar **first, gchar **second) G_GNUC_WARN_UNUSED_RESULT; -gchar **utils_strv_shorten_file_list(gchar **file_names, gssize num); +gchar **utils_strv_shorten_file_list(gchar **file_names, gssize file_names_len); GSList *utils_get_config_files(const gchar *subdir);