diff --git a/src/utils.c b/src/utils.c index 3259bd82..d51c10ae 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);