codemaid
codemaid copied to clipboard
Sorting isn't using natural sort
Environment
- Visual Studio version: 2019 Enterprise
- CodeMaid version: 11.0
- Code language: C#
Description
CodeMaid appears to be using logical sort when ordering members, therefore member names with numbers can get ordered incorrectly for a human.
Steps to recreate
- Create methods containing at least one double digit number, e.g.
public void Scenario8Test2()
{
}
public void Scenario8Test10()
{
}
- Instruct CodeMaid to organise the document
Current behavior
CodeMaid will order logically, so using the above example you'll end up with
public void Scenario8Test10()
{
}
public void Scenario8Test2()
{
}
Not exactly the way a human would order them :)
Expected behavior
It would be nicer if CodeMaid used natural sorting for member names.
Why are you creating methods with numbers in them?
I'm not a monster, really! SpecFlow auto generates test methods that are worse, and as the order is important for readability purposes (because each "test" has multiple methods with a different condition) I can't cleanup the file using its default names. I've started renaming them by scenario and step so that when I organise the file they still appear in the correct order (and to stop me going batty reading the names it does produce). The method names don't entirely matter as there's a bunch of Given, When, Then attributes for each test method with a textual description.
As the Test Explorer was ordering the same way, I ended up getting sufficiently irritated with everything that I prefixed all the single digit numbers with a leading zero, that convinces everything to order in the way I'd like.
Thanks for reporting the issue. You're right that we are using logical sort and that doesn't match to the way a human would do it. A great example is a Start method followed by an End method. A human would traditionally put Start before End but there's no alphabetical based sort that would.
There have been some conversations in the past around custom reorganization rules to allow for explicitly telling CodeMaid an intended order of the methods (i.e. ignore the rules, put B before A) but I don't think there was very much consensus on how that would be done and if people would take the time to add custom attributes/comments or if they'd find a shortcut (e.g. your zero padding).
It would be sufficient for the beginning to use an alphanumeric sort.
Hello,
Thanks for the reply. I personally would find it very odd if the ordering was anything other than alphabetically (but that is of course entirely my opinion), my point in raising the issue was specifically in regarding to numbers as I don't think anybody would reasonably think 10, 11, 2 is a valid order. However, the zero padding works perfectly well and is a practical alternative to a complex system that possibly few would ever use (not to mention that as evidenced by the Test Runner window, other functionality would be blissfully unaware). It would however be nice if future versions did allow for a natural sort rather than a logical, but given the workaround it's just a rainy day nice to have, potentially.
Thanks again for taking the time to respond.
Regards; Richard Moss
Thanks for the comments. We are using the default string comparison sort (see https://github.com/codecadwallader/codemaid/blob/master/CodeMaid/Integration/Commands/SortLinesCommand.cs#L106) and this matches the sort behavior you would get in other editors (e.g. Visual Studio Code). A little web searching indicates a custom comparer would need to be created to achieve alphanumeric sort (https://stackoverflow.com/questions/6396378/c-sharp-linq-orderby-numbers-that-are-string-and-you-cannot-convert-them-to-int). I'm definitely open to having one of those as an option that can be enabled by users if someone is interested in pursuing. :)
Hello @codecadwallader
I'm looking into this, but I need some guidance, as its my first time here. :)
First, where do you think we should put this option? How about Option > General > Features?

Next, I've done some research about the sorting algorithm and found this one: https://github.com/mudzereli/mudsort/blob/master/AlphanumComparator.cs. The licenses are compatible (LGPL 2.1 and LGPL 3.0). Is it ok to use that code? How should I manage the legal notice, just keep it at the top of the file as is?
Thanks.
I think it could be useful to add this sorting to the "Reorganizing" feature as well.
Maybe change that from a checkbox to a combobox named "Sort members of the same group" with 3 options: "Keep Order (or don't sort)", "Sort Alphabetically" and "Sort Alphanumerically" (is this a word?).
Any thoughts?
Hello @SacuL , thanks for asking the questions and taking an interest. :)
I think it would be most consistent with other features to add a "Sorting" item to the tree after Reorganizing and before Switching. I'm thinking if someone opted into that feature they would likely want it to apply across the board (i.e. including reorganizing). What do you think?
In regards to pulling in another code base, so far we haven't done that so there isn't a place to include other licenses and I'm not super comfortable with the legal aspects of it. I know it should be fine, but I also don't know enough to confirm that.
Grouping in a single "Sorting" menu looks better, I will do that.
For the sorting I'll implement my own version so we won't have to pull anything.
What is the point of having formatting if it is not for improving readability? If it is for readability then what is the most readable format really? Personally I have some doubts (maybe even objections) regarding to sorting things alphabetically, in this case, the 2 methods that you have used in another method might be placed far ends of the code, which makes readability worst, doesn't it?
I don't know if this is still being worked on. Below is a class that can be used to do an alphanumeric sort where it treats digits as numbers on Windows. As you can see, it uses the CompareStringEx Windows API function. One way to use it is like this:
if (StringCompareHelper.StaticCompare(selectedText, sortedText) != 0)
{
}
Here is the class:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace HWSPluginInterface
{
public class StringComparerHelper : IComparer<string>
{
#pragma warning disable 414
private static readonly Int32 NORM_IGNORECASE = 0x00000001;
private static readonly Int32 NORM_IGNORENONSPACE = 0x00000002;
private static readonly Int32 NORM_IGNORESYMBOLS = 0x00000004;
private static readonly Int32 LINGUISTIC_IGNORECASE = 0x00000010;
private static readonly Int32 LINGUISTIC_IGNOREDIACRITIC = 0x00000020;
private static readonly Int32 NORM_IGNOREKANATYPE = 0x00010000;
private static readonly Int32 NORM_IGNOREWIDTH = 0x00020000;
private static readonly Int32 NORM_LINGUISTIC_CASING = 0x08000000;
private static readonly Int32 SORT_STRINGSORT = 0x00001000;
private static readonly Int32 SORT_DIGITSASNUMBERS = 0x00000008;
private static readonly String LOCALE_NAME_USER_DEFAULT = null;
private static readonly String LOCALE_NAME_INVARIANT = String.Empty;
private static readonly String LOCALE_NAME_SYSTEM_DEFAULT = "!sys-default-locale";
#pragma warning restore 414
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern Int32 CompareStringEx(
String localeName,
Int32 flags,
String str1,
Int32 count1,
String str2,
Int32 count2,
IntPtr versionInformation,
IntPtr reserved,
Int32 param
);
public static int StaticCompare(String x, String y)
{
// CompareStringEx return 1, 2, or 3. Subtract 2 to get the return value.
return CompareStringEx(
LOCALE_NAME_USER_DEFAULT,
SORT_DIGITSASNUMBERS, // Add other flags if required.
x,
x.Length,
y,
y.Length,
IntPtr.Zero,
IntPtr.Zero,
0) - 2;
}
public int Compare(String x, String y)
{
return StaticCompare(x, y);
}
}
}