From e4cc6e69430049366434e3383b1d2ef283ed22cc Mon Sep 17 00:00:00 2001 From: Eren Karakas Date: Tue, 19 Nov 2024 09:08:45 +0000 Subject: natural sort: exclude extension when comparing filenames Currently natural sort compares the entire filenames (basename.extension) when sorting. This causes eg. "a 2.txt" to appear before "a.txt" when sorted by ascending. This is unintuitive since people prioritize basenames more than file extensions. Instead, change natural sort to compare by basename only and fallback to comparing extensions if basenames were equal. This change causes "a.txt" to appear before "a 2.txt" and matches how other platforms such as GNOME and Windows behave. BUG: 416025 BUG: 470538 BUG: 421869 BUG: 312027 --- src/kitemviews/kfileitemmodel.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'src/kitemviews/kfileitemmodel.cpp') diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index fb5851c36..f8c30b9e4 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -2249,7 +2249,24 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol QMutexLocker collatorLock(s_collatorMutex()); if (m_naturalSorting) { - return collator.compare(a, b); + // Split extension, taking into account it can be empty + constexpr QString::SectionFlags flags = QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep; + + // Sort by baseName first + const QString aBaseName = a.section('.', 0, 0, flags); + const QString bBaseName = b.section('.', 0, 0, flags); + + const int res = collator.compare(aBaseName, bBaseName); + if (res != 0 || (aBaseName.length() == a.length() && bBaseName.length() == b.length())) { + return res; + } + + // sliced() has undefined behavior when pos < 0 or pos > size(). + Q_ASSERT(aBaseName.length() <= a.length() && aBaseName.length() >= 0); + Q_ASSERT(bBaseName.length() <= b.length() && bBaseName.length() >= 0); + + // baseNames were equal, sort by extension + return collator.compare(a.sliced(aBaseName.length()), b.sliced(bBaseName.length())); } const int result = QString::compare(a, b, collator.caseSensitivity()); -- cgit v1.3