From 4116fcbcddd2e5d87a9d935af3a129b969f87355 Mon Sep 17 00:00:00 2001 From: samabcde Date: Wed, 10 May 2023 00:21:20 +0800 Subject: [PATCH] VIM-2615 add support to sort u command, fix natural sort issue when both string not contain number --- .../maddyhome/idea/vim/group/ChangeGroup.java | 36 +- .../commands/SortCommandTest.kt | 701 +++++++++++++++--- .../maddyhome/idea/vim/api/VimChangeGroup.kt | 3 +- .../vimscript/model/commands/SortCommand.kt | 45 +- 4 files changed, 664 insertions(+), 121 deletions(-) diff --git a/src/main/java/com/maddyhome/idea/vim/group/ChangeGroup.java b/src/main/java/com/maddyhome/idea/vim/group/ChangeGroup.java index 890bdeaa13..c41369c71b 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/ChangeGroup.java +++ b/src/main/java/com/maddyhome/idea/vim/group/ChangeGroup.java @@ -8,7 +8,6 @@ package com.maddyhome.idea.vim.group; import com.google.common.base.Splitter; -import com.google.common.collect.Lists; import com.intellij.codeInsight.actions.AsyncActionExecutionService; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.DataContext; @@ -49,6 +48,7 @@ import com.maddyhome.idea.vim.newapi.IjEditorExecutionContextKt; import com.maddyhome.idea.vim.newapi.IjVimCaret; import com.maddyhome.idea.vim.newapi.IjVimEditor; +import com.maddyhome.idea.vim.vimscript.model.commands.SortOption; import kotlin.Pair; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -58,10 +58,9 @@ import org.jetbrains.annotations.TestOnly; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import static com.maddyhome.idea.vim.api.VimInjectorKt.injector; import static com.maddyhome.idea.vim.api.VimInjectorKt.options; @@ -500,9 +499,11 @@ public void indentRange(@NotNull VimEditor editor, * @param editor The editor to replace text in * @param range The range to sort * @param lineComparator The comparator to use to sort + * @param sortOption The option to sort the range * @return true if able to sort the text, false if not */ - public boolean sortRange(@NotNull VimEditor editor, @NotNull VimCaret caret, @NotNull LineRange range, @NotNull Comparator lineComparator) { + public boolean sortRange(@NotNull VimEditor editor, @NotNull VimCaret caret, @NotNull LineRange range, @NotNull Comparator lineComparator, + @NotNull SortOption sortOption) { final int startLine = range.startLine; final int endLine = range.endLine; final int count = endLine - startLine + 1; @@ -513,7 +514,7 @@ public boolean sortRange(@NotNull VimEditor editor, @NotNull VimCaret caret, @No final int startOffset = editor.getLineStartOffset(startLine); final int endOffset = editor.getLineEndOffset(endLine); - return sortTextRange(editor, caret, startOffset, endOffset, lineComparator); + return sortTextRange(editor, caret, startOffset, endOffset, lineComparator, sortOption); } /** @@ -523,19 +524,34 @@ public boolean sortRange(@NotNull VimEditor editor, @NotNull VimCaret caret, @No * @param start The starting position for the sort * @param end The ending position for the sort * @param lineComparator The comparator to use to sort + * @param sortOption The option to sort the range * @return true if able to sort the text, false if not */ private boolean sortTextRange(@NotNull VimEditor editor, @NotNull VimCaret caret, int start, int end, - @NotNull Comparator lineComparator) { + @NotNull Comparator lineComparator, + @NotNull SortOption sortOption) { final String selectedText = ((IjVimEditor) editor).getEditor().getDocument().getText(new TextRangeInterval(start, end)); - final List lines = Lists.newArrayList(Splitter.on("\n").split(selectedText)); + final List lines = StreamSupport.stream(Splitter.on("\n").split(selectedText).spliterator(), false).sorted(lineComparator) + .collect(Collectors.toCollection(ArrayList::new)); + if (sortOption.getUnique()) { + Iterator iterator = lines.iterator(); + String previous = null; + while (iterator.hasNext()) { + String current = iterator.next(); + if (current.equals(previous) || sortOption.getIgnoreCase() && current.equalsIgnoreCase(previous)) { + iterator.remove(); + } + else { + previous = current; + } + } + } if (lines.size() < 1) { return false; } - lines.sort(lineComparator); replaceText(editor, caret, start, end, StringUtil.join(lines, "\n")); return true; } diff --git a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SortCommandTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SortCommandTest.kt index 612389427c..e29d71cd41 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SortCommandTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SortCommandTest.kt @@ -13,116 +13,631 @@ import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.VimTestCase import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource import javax.swing.KeyStroke @Suppress("SpellCheckingInspection") class SortCommandTest : VimTestCase() { - @Test - fun testBasicSort() { - configureByText( - """ - Test - Hello World! - - """.trimIndent(), - ) - val keys: MutableList = Lists.newArrayList(KeyStroke.getKeyStroke("control V")) - keys.addAll(injector.parser.stringToKeys("\$j")) - typeText(keys) - typeText(commandToKeys("sort")) - assertState( - """ - Hello World! - Test - - """.trimIndent(), - ) + private fun assertSort(testCase: TestCase) { + val (content, visualSelect, sortCommand, expected) = testCase + configureByText(content) + if (visualSelect.isNotBlank()) { + val keys: MutableList = Lists.newArrayList(KeyStroke.getKeyStroke("control V")) + keys.addAll(injector.parser.stringToKeys(visualSelect)) + typeText(keys) + } + typeText(commandToKeys(sortCommand)) + assertState(expected) } - @Test - fun testMultipleSortLine() { - configureByText("zee\nyee\na\nb\n") - val keys: MutableList = Lists.newArrayList(KeyStroke.getKeyStroke("control V")) - keys.addAll(injector.parser.stringToKeys("$3j")) - typeText(keys) - typeText(commandToKeys("sort")) - assertState("a\nb\nyee\nzee\n") - } + data class TestCase(val content: String, val visualSelect: String = "", val sortCommand: String, val expected: String) - @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) - @Test - fun testInverseSort() { - configureByText("kay\nzee\nyee\na\nb\n") - val keys: MutableList = Lists.newArrayList(KeyStroke.getKeyStroke("control V")) - keys.addAll(injector.parser.stringToKeys("$4j")) - typeText(keys) - typeText(commandToKeys("sort !")) - assertState("zee\nyee\nkay\nb\na\n") - } + companion object { + @JvmStatic + fun defaultSortTestCases(): List { + return listOf( + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + """.trimIndent(), + sortCommand = "sort", + expected = """ + 10 + 2 + AB + IGNORE_CASE_DUPLICATE + ac + duplicate + duplicate + ignore_case_duplicate + """.trimIndent() + ), + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + visualSelect = "$7j", + sortCommand = "sort", + expected = """ + 10 + 2 + AB + IGNORE_CASE_DUPLICATE + ac + duplicate + duplicate + ignore_case_duplicate + a + """.trimIndent() + ), + TestCase( + content = """ + z + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + sortCommand = "2,9sort", + expected = """ + z + 10 + 2 + AB + IGNORE_CASE_DUPLICATE + ac + duplicate + duplicate + ignore_case_duplicate + a + """.trimIndent() + ) + ) + } - @Test - fun testCaseSensitiveSort() { - configureByText("apple\nAppetite\nApp\napparition\n") - val keys: MutableList = Lists.newArrayList(KeyStroke.getKeyStroke("control V")) - keys.addAll(injector.parser.stringToKeys("$3j")) - typeText(keys) - typeText(commandToKeys("sort")) - assertState("App\nAppetite\napparition\napple\n") - } + @JvmStatic + fun numericSortTestCases(): List { + return listOf( + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + """.trimIndent(), + sortCommand = "sort n", + expected = """ + AB + IGNORE_CASE_DUPLICATE + ac + duplicate + duplicate + ignore_case_duplicate + 2 + 10 + """.trimIndent() + ), + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + visualSelect = "$7j", + sortCommand = "sort n", + expected = """ + AB + IGNORE_CASE_DUPLICATE + ac + duplicate + duplicate + ignore_case_duplicate + 2 + 10 + a + """.trimIndent() + ), + TestCase( + content = """ + z + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + sortCommand = "2,9sort n", + expected = """ + z + AB + IGNORE_CASE_DUPLICATE + ac + duplicate + duplicate + ignore_case_duplicate + 2 + 10 + a + """.trimIndent() + ) + ) + } - @Test - fun testCaseInsensitiveSort() { - configureByText("apple\nAppetite\nApp\napparition\n") - val keys: MutableList = Lists.newArrayList(KeyStroke.getKeyStroke("control V")) - keys.addAll(injector.parser.stringToKeys("$3j")) - typeText(keys) - typeText(commandToKeys("sort i")) - assertState("App\napparition\nAppetite\napple\n") - } + @JvmStatic + fun caseInsensitiveSortTestCases(): List { + return listOf( + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + """.trimIndent(), + sortCommand = "sort i", + expected = """ + 10 + 2 + AB + ac + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + """.trimIndent() + ), + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + visualSelect = "$7j", + sortCommand = "sort i", + expected = """ + 10 + 2 + AB + ac + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent() + ), + TestCase( + content = """ + z + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + sortCommand = "2,9sort i", + expected = """ + z + 10 + 2 + AB + ac + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent() + ) + ) + } - @Test - fun testRangeSort() { - configureByText("zee\nc\na\nb\nwhatever\n") - typeText(commandToKeys("2,4sort")) - assertState("zee\na\nb\nc\nwhatever\n") - } + @JvmStatic + fun reverseSortTestCases(): List { + return listOf( + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + """.trimIndent(), + sortCommand = "sort!", + expected = """ + ignore_case_duplicate + duplicate + duplicate + ac + IGNORE_CASE_DUPLICATE + AB + 2 + 10 + """.trimIndent() + ), + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + visualSelect = "$7j", + sortCommand = "sort!", + expected = """ + ignore_case_duplicate + duplicate + duplicate + ac + IGNORE_CASE_DUPLICATE + AB + 2 + 10 + a + """.trimIndent() + ), + TestCase( + content = """ + z + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + sortCommand = "2,9sort!", + expected = """ + z + ignore_case_duplicate + duplicate + duplicate + ac + IGNORE_CASE_DUPLICATE + AB + 2 + 10 + a + """.trimIndent() + ) + ) + } - @Test - fun testNumberSort() { - configureByText("120\n70\n30\n2000") - typeText(commandToKeys("sort n")) - assertState("30\n70\n120\n2000") - } + @JvmStatic + fun uniqueSortTestCases(): List { + return listOf( + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + """.trimIndent(), + sortCommand = "sort u", + expected = """ + 10 + 2 + AB + IGNORE_CASE_DUPLICATE + ac + duplicate + ignore_case_duplicate + """.trimIndent() + ), + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + visualSelect = "$7j", + sortCommand = "sort u", + expected = """ + 10 + 2 + AB + IGNORE_CASE_DUPLICATE + ac + duplicate + ignore_case_duplicate + a + """.trimIndent() + ), + TestCase( + content = """ + z + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + sortCommand = "2,9sort u", + expected = """ + z + 10 + 2 + AB + IGNORE_CASE_DUPLICATE + ac + duplicate + ignore_case_duplicate + a + """.trimIndent() + ) + ) + } - @Test - fun testNaturalOrderSort() { - configureByText("hello1000\nhello102\nhello70000\nhello1001") - typeText(commandToKeys("sort n")) - assertState("hello102\nhello1000\nhello1001\nhello70000") - } + @JvmStatic + fun caseInsensitiveUniqueSortTestCases(): List { + return listOf( + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + """.trimIndent(), + sortCommand = "sort iu", + expected = """ + 10 + 2 + AB + ac + duplicate + ignore_case_duplicate + """.trimIndent() + ), + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + visualSelect = "$7j", + sortCommand = "sort iu", + expected = """ + 10 + 2 + AB + ac + duplicate + ignore_case_duplicate + a + """.trimIndent() + ), + TestCase( + content = """ + z + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + sortCommand = "2,9sort iu", + expected = """ + z + 10 + 2 + AB + ac + duplicate + ignore_case_duplicate + a + """.trimIndent() + ) + ) + } - @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) - @Test - fun testNaturalOrderReverseSort() { - configureByText("hello1000\nhello102\nhello70000\nhello1001") - typeText(commandToKeys("sort n!")) - assertState("hello70000\nhello1001\nhello1000\nhello102") + @JvmStatic + fun numericCaseInsensitiveReverseUniqueSortTestCases(): List { + return listOf( + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + """.trimIndent(), + sortCommand = "sort! niu", + expected = """ + 10 + 2 + ignore_case_duplicate + duplicate + ac + AB + """.trimIndent() + ), + TestCase( + content = """ + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + visualSelect = "$7j", + sortCommand = "sort! niu", + expected = """ + 10 + 2 + ignore_case_duplicate + duplicate + ac + AB + a + """.trimIndent() + ), + TestCase( + content = """ + z + ac + AB + 10 + 2 + duplicate + duplicate + ignore_case_duplicate + IGNORE_CASE_DUPLICATE + a + """.trimIndent(), + sortCommand = "2,9sort! niu", + expected = """ + z + 10 + 2 + ignore_case_duplicate + duplicate + ac + AB + a + """.trimIndent() + ) + ) + } } + + @ParameterizedTest + @MethodSource("defaultSortTestCases") + fun `test default sort is case sensitive, not numeric, ascending and not unique`( + testCase: TestCase, + ) = assertSort(testCase) + + @ParameterizedTest + @MethodSource("numericSortTestCases") + fun `test numeric sort is case sensitive, numeric, ascending and not unique`( + testCase: TestCase, + ) = assertSort(testCase) + + @ParameterizedTest + @MethodSource("caseInsensitiveSortTestCases") + fun `test case insensive sort is case insensitive, not numeric, ascending and not unique`( + testCase: TestCase, + ) = assertSort(testCase) + @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) - @Test - fun testNaturalOrderInsensitiveReverseSort() { - configureByText("Hello1000\nhello102\nhEllo70000\nhello1001") - typeText(commandToKeys("sort ni!")) - assertState("hEllo70000\nhello1001\nHello1000\nhello102") - } + @ParameterizedTest + @MethodSource("reverseSortTestCases") + fun `test reverse sort is case sensitive, not numeric, descending and not unique`( + testCase: TestCase, + ) = assertSort(testCase) - @Test - fun testGlobalSort() { - configureByText("zee\nc\na\nb\nwhatever") - typeText(commandToKeys("sort")) - assertState("a\nb\nc\nwhatever\nzee") - } + @ParameterizedTest + @MethodSource("uniqueSortTestCases") + fun `test unique sort is case sensitive, not numeric, ascending and unique`( + testCase: TestCase, + ) = assertSort(testCase) + + @ParameterizedTest + @MethodSource("caseInsensitiveUniqueSortTestCases") + fun `test case insensitive unique sort is case insensitive, not numeric, ascending and unique`( + testCase: TestCase, + ) = assertSort(testCase) + + @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) + @ParameterizedTest + @MethodSource("numericCaseInsensitiveReverseUniqueSortTestCases") + fun `test numeric, case insensitive, reverse and unique sort is case insensitive, numeric, descending and unique`( + testCase: TestCase, + ) = assertSort(testCase) @Test fun testSortWithPrecedingWhiteSpace() { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt index 1bf805a6db..de4cfa79f5 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt @@ -15,6 +15,7 @@ import com.maddyhome.idea.vim.command.VimStateMachine import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.ex.ranges.LineRange import com.maddyhome.idea.vim.group.visual.VimSelection +import com.maddyhome.idea.vim.vimscript.model.commands.SortOption import org.jetbrains.annotations.TestOnly import javax.swing.KeyStroke @@ -163,7 +164,7 @@ public interface VimChangeGroup { public fun changeNumber(editor: VimEditor, caret: VimCaret, count: Int): Boolean - public fun sortRange(editor: VimEditor, caret: VimCaret, range: LineRange, lineComparator: Comparator): Boolean + public fun sortRange(editor: VimEditor, caret: VimCaret, range: LineRange, lineComparator: Comparator, sortOptions: SortOption): Boolean public fun reset() diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/SortCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/SortCommand.kt index 590c75d58d..5cd2e9cd7f 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/SortCommand.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/SortCommand.kt @@ -32,16 +32,17 @@ public data class SortCommand(val ranges: Ranges, val argument: String) : Comman override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult { val arg = argument val nonEmptyArg = arg.trim().isNotEmpty() - - val reverse = nonEmptyArg && "!" in arg - val ignoreCase = nonEmptyArg && "i" in arg - val number = nonEmptyArg && "n" in arg - - val lineComparator = LineComparator(ignoreCase, number, reverse) + val sortOption = SortOption( + reverse = nonEmptyArg && "!" in arg, + ignoreCase = nonEmptyArg && "i" in arg, + numeric = nonEmptyArg && "n" in arg, + unique = nonEmptyArg && "u" in arg, + ) + val lineComparator = LineComparator(sortOption.ignoreCase, sortOption.numeric, sortOption.reverse) if (editor.inBlockSubMode) { val primaryCaret = editor.primaryCaret() val range = getSortLineRange(editor, primaryCaret) - val worked = injector.changeGroup.sortRange(editor, primaryCaret, range, lineComparator) + val worked = injector.changeGroup.sortRange(editor, primaryCaret, range, lineComparator, sortOption) primaryCaret.moveToInlayAwareOffset( injector.motion.moveCaretToLineStartSkipLeading(editor, range.startLine), ) @@ -51,7 +52,7 @@ public data class SortCommand(val ranges: Ranges, val argument: String) : Comman var worked = true for (caret in editor.nativeCarets()) { val range = getSortLineRange(editor, caret) - if (!injector.changeGroup.sortRange(editor, caret, range, lineComparator)) { + if (!injector.changeGroup.sortRange(editor, caret, range, lineComparator, sortOption)) { worked = false } caret.moveToInlayAwareOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, range.startLine)) @@ -87,31 +88,34 @@ public data class SortCommand(val ranges: Ranges, val argument: String) : Comman } private class LineComparator( - private val myIgnoreCase: Boolean, - private val myNumber: Boolean, - private val myReverse: Boolean, + private val ignoreCase: Boolean, + private val numeric: Boolean, + private val reverse: Boolean, ) : Comparator { override fun compare(o1: String, o2: String): Int { var o1ToCompare = o1 var o2ToCompare = o2 - if (myReverse) { + if (reverse) { val tmp = o2ToCompare o2ToCompare = o1ToCompare o1ToCompare = tmp } - if (myIgnoreCase) { + if (ignoreCase) { o1ToCompare = o1ToCompare.uppercase(Locale.getDefault()) o2ToCompare = o2ToCompare.uppercase(Locale.getDefault()) } - return if (myNumber) { - // About natural sort order - http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html + return if (numeric) { + // About natural sort order - https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/ val n1 = injector.searchGroup.findDecimalNumber(o1ToCompare) val n2 = injector.searchGroup.findDecimalNumber(o2ToCompare) if (n1 == null) { - if (n2 == null) 0 else -1 + if (n2 == null) { + // no number, fallback to default + o1ToCompare.compareTo(o2ToCompare) + } else -1 } else { - if (n2 == null) 1 else n1.compareTo(n2) + if (n2 == null) 1 else n1.compareTo(n2) // what if tied? } } else { o1ToCompare.compareTo(o2ToCompare) @@ -119,3 +123,10 @@ public data class SortCommand(val ranges: Ranges, val argument: String) : Comman } } } + +public data class SortOption( + val ignoreCase: Boolean, + val numeric: Boolean, + val reverse: Boolean, + val unique: Boolean, +)