Skip to content

Commit b6409fe

Browse files
committed
Fix: natural string sorting to correctly handle file extensions
1 parent acfabea commit b6409fe

File tree

1 file changed

+58
-23
lines changed

1 file changed

+58
-23
lines changed

src/Files.App/Helpers/NaturalStringComparer.cs

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -61,39 +61,74 @@ public int Compare(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y)
6161
}
6262

6363
public static int Compare(ReadOnlySpan<char> x, ReadOnlySpan<char> y, StringComparison stringComparison)
64-
{
65-
var length = Math.Min(x.Length, y.Length);
64+
{
65+
// Handle file extensions specially
66+
int xExtPos = GetExtensionPosition(x);
67+
int yExtPos = GetExtensionPosition(y);
68+
69+
// If both have extensions, compare the names first
70+
if (xExtPos >= 0 && yExtPos >= 0)
71+
{
72+
var xName = x.Slice(0, xExtPos);
73+
var yName = y.Slice(0, yExtPos);
74+
75+
int nameCompare = CompareWithoutExtension(xName, yName, stringComparison);
76+
if (nameCompare != 0)
77+
return nameCompare;
78+
79+
// If names match, compare extensions
80+
return x.Slice(xExtPos).CompareTo(y.Slice(yExtPos), stringComparison);
81+
}
82+
83+
// Original comparison logic for non-extension cases
84+
return CompareWithoutExtension(x, y, stringComparison);
85+
}
6686

67-
for (var i = 0; i < length; i++)
68-
{
87+
private static int CompareWithoutExtension(ReadOnlySpan<char> x, ReadOnlySpan<char> y, StringComparison stringComparison)
88+
{
89+
var length = Math.Min(x.Length, y.Length);
90+
91+
for (var i = 0; i < length; i++)
92+
{
6993
while (i < x.Length && i < y.Length && IsIgnorableSeparator(x, i) && IsIgnorableSeparator(y, i))
70-
i++;
94+
i++;
7195

72-
if (i >= x.Length || i >= y.Length) break;
96+
if (i >= x.Length || i >= y.Length) break;
7397

74-
if (char.IsDigit(x[i]) && char.IsDigit(y[i]))
75-
{
76-
var xOut = GetNumber(x.Slice(i), out var xNumAsSpan);
77-
var yOut = GetNumber(y.Slice(i), out var yNumAsSpan);
98+
if (char.IsDigit(x[i]) && char.IsDigit(y[i]))
99+
{
100+
var xOut = GetNumber(x.Slice(i), out var xNumAsSpan);
101+
var yOut = GetNumber(y.Slice(i), out var yNumAsSpan);
78102

79-
var compareResult = CompareNumValues(xNumAsSpan, yNumAsSpan);
103+
var compareResult = CompareNumValues(xNumAsSpan, yNumAsSpan);
80104

81-
if (compareResult != 0) return compareResult;
105+
if (compareResult != 0) return compareResult;
82106

83-
i = -1;
84-
length = Math.Min(xOut.Length, yOut.Length);
107+
i = -1;
108+
length = Math.Min(xOut.Length, yOut.Length);
85109

86-
x = xOut;
87-
y = yOut;
88-
continue;
89-
}
110+
x = xOut;
111+
y = yOut;
112+
continue;
113+
}
90114

91-
var charCompareResult = x.Slice(i, 1).CompareTo(y.Slice(i, 1), stringComparison);
92-
if (charCompareResult != 0) return charCompareResult;
93-
}
115+
var charCompareResult = x.Slice(i, 1).CompareTo(y.Slice(i, 1), stringComparison);
116+
if (charCompareResult != 0) return charCompareResult;
117+
}
94118

95-
return x.Length.CompareTo(y.Length);
96-
}
119+
return x.Length.CompareTo(y.Length);
120+
}
121+
122+
private static int GetExtensionPosition(ReadOnlySpan<char> text)
123+
{
124+
// Find the last period that's not at the beginning
125+
for (int i = text.Length - 1; i > 0; i--)
126+
{
127+
if (text[i] == '.')
128+
return i;
129+
}
130+
return -1;
131+
}
97132

98133
private static bool IsIgnorableSeparator(ReadOnlySpan<char> span, int index)
99134
{

0 commit comments

Comments
 (0)