From cecb0ebbbc69766ae37cc75daf5a5bd5b5dd5d6b Mon Sep 17 00:00:00 2001 From: Fabian Homborg Date: Fri, 14 Feb 2020 18:44:54 +0100 Subject: [PATCH] Return glob ordering to pre-3.1 state Glob ordering is used in a variety of places, including figuring out conf.d and really needs to be stable. Other ordering, like completions, is really just cosmetic and can change if it makes for a nicer experience. So we uncouple it by copying the wcsfilecmp from 3.0.2, which will return the ordering to what it was in that release. Fixes #6593 (cherry picked from commit f053cd27c61c687c19b80b18dd0ee9331effb890) --- src/expand.cpp | 7 ++++++- src/util.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ src/util.h | 3 +++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/expand.cpp b/src/expand.cpp index 3cb203e6e..56cb0018f 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -45,6 +45,7 @@ #include "path.h" #include "proc.h" #include "reader.h" +#include "util.h" #include "wcstringutil.h" #include "wildcard.h" #include "wutil.h" // IWYU pragma: keep @@ -1017,7 +1018,11 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_ } } - std::sort(expanded.begin(), expanded.end(), completion_t::is_naturally_less_than); + std::sort(expanded.begin(), expanded.end(), + [&](const completion_t &a, const completion_t &b) { + return wcsfilecmp_glob(a.completion.c_str(), b.completion.c_str()) < 0; + }); + std::move(expanded.begin(), expanded.end(), std::back_inserter(*out)); } else { // Can't fully justify this check. I think it's that SKIP_WILDCARDS is used when completing diff --git a/src/util.cpp b/src/util.cpp index dcbbc2eac..c23404783 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -100,6 +100,55 @@ int wcsfilecmp(const wchar_t *a, const wchar_t *b) { return 1; // string b is a prefix of a and a is longer } +/// wcsfilecmp, but frozen in time for glob usage. +int wcsfilecmp_glob(const wchar_t *a, const wchar_t *b) { + assert(a && b && "Null parameter"); + const wchar_t *orig_a = a; + const wchar_t *orig_b = b; + int retval = 0; // assume the strings will be equal + + while (*a && *b) { + if (iswdigit(*a) && iswdigit(*b)) { + retval = wcsfilecmp_leading_digits(&a, &b); + // If we know the strings aren't logically equal or we've reached the end of one or both + // strings we can stop iterating over the chars in each string. + if (retval || *a == 0 || *b == 0) break; + } + + wint_t al = towlower(*a); + wint_t bl = towlower(*b); + if (al < bl) { + retval = -1; + break; + } else if (al > bl) { + retval = 1; + break; + } else { + a++; + b++; + } + } + + if (retval != 0) return retval; // we already know the strings aren't logically equal + + if (*a == 0) { + if (*b == 0) { + // The strings are logically equal. They may or may not be the same length depending on + // whether numbers were present but that doesn't matter. Disambiguate strings that + // differ by letter case or length. We don't bother optimizing the case where the file + // names are literally identical because that won't occur given how this function is + // used. And even if it were to occur (due to being reused in some other context) it + // would be so rare that it isn't worth optimizing for. + retval = wcscmp(orig_a, orig_b); + return retval < 0 ? -1 : retval == 0 ? 0 : 1; + } + return -1; // string a is a prefix of b and b is longer + } + + assert(*b == 0); + return 1; // string b is a prefix of a and a is longer +} + /// Return microseconds since the epoch. long long get_time() { struct timeval time_struct; diff --git a/src/util.h b/src/util.h index f9f121849..5cfb71270 100644 --- a/src/util.h +++ b/src/util.h @@ -31,6 +31,9 @@ /// given above. int wcsfilecmp(const wchar_t *a, const wchar_t *b); +/// wcsfilecmp, but frozen in time for glob usage. +int wcsfilecmp_glob(const wchar_t *a, const wchar_t *b); + /// Get the current time in microseconds since Jan 1, 1970. long long get_time();