├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── Default.sublime-commands ├── Main.sublime-menu ├── README.md ├── SortNumericallyCommand.py ├── dependencies.json ├── sort_numerically.py └── tests.py /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master, ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 11 * * 0' 11 | 12 | jobs: 13 | analyse: 14 | name: Analyse 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | with: 21 | # We must fetch at least the immediate parents so that if this is 22 | # a pull request then we can checkout the head. 23 | fetch-depth: 2 24 | 25 | # If this run was triggered by a pull request event, then checkout 26 | # the head of the pull request instead of the merge commit. 27 | - run: git checkout HEAD^2 28 | if: ${{ github.event_name == 'pull_request' }} 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v1 33 | # Override language selection by uncommenting this and choosing your languages 34 | # with: 35 | # languages: go, javascript, csharp, python, cpp, java 36 | 37 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 38 | # If this step fails, then you should remove it and run the build manually (see below) 39 | - name: Autobuild 40 | uses: github/codeql-action/autobuild@v1 41 | 42 | # ℹ️ Command-line programs to run using the OS shell. 43 | # 📚 https://git.io/JvXDl 44 | 45 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 46 | # and modify them (or add more) to build your code if your project 47 | # uses a compiled language 48 | 49 | #- run: | 50 | # make bootstrap 51 | # make release 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v1 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["alt+f9"], "command": "sort_numerically"} 3 | ] 4 | -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["alt+f5"], "command": "sort_numerically"} 3 | ] 4 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["alt+f9"], "command": "sort_numerically"} 3 | ] 4 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Sort Lines (Numerically)", 4 | "command": "sort_numerically" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "edit", 4 | "children": 5 | [ 6 | { "id": "permute" }, 7 | { "command": "sort_numerically", "caption": "Sort Lines (Numerically)", "mnemonic": "n" } 8 | ] 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sort Lines (Numerically) 2 | 3 | Sublime Text 2/3 package that adds a command for sorting lines numerically rather than alphabetically. 4 | 5 | Installation 6 | ------------ 7 | This package is available through [Package Control](https://sublime.wbond.net/), it’s called “Sort Lines (Numerically)”. 8 | 9 | Usage 10 | ----- 11 | Just run “Sort Lines (Numerically)” from the Command Palette, or from the Edit menu, which has a keyboard shortcut as well. 12 | 13 | Adding a reverse command 14 | ------------------------ 15 | This package does not have a reverse option when sorting, since there is already a `Reverse` command built into Sublime Text. If you want a shortcut for sorting numerically in reverse, you can make one yourself: 16 | 17 | In your `User` package folder, create a file called `Sort Lines (Numerically) Reverse.sublime-macro`. Edit that file, paste in the following, and save: 18 | 19 | ``` 20 | [ 21 | { 22 | "args": null, 23 | "command": "sort_numerically" 24 | }, 25 | { 26 | "args": { 27 | "operation": "reverse" 28 | }, 29 | "command": "permute_lines" 30 | } 31 | ] 32 | ``` 33 | 34 | There will now be a new menu item at `Tools` > `Macros` > `User` > `Sort Lines (Numerically) Reverse` that when selected will sort numerically and reverse the order. If you want this macro available in the command palette, create a file in your `User` folder called something like `SortLinesNumericallyReverse.sublime-commands` containing the following: 35 | 36 | ``` 37 | [ 38 | { 39 | "caption": "Sort Lines (Numerically) Reverse", 40 | "command": "run_macro_file", 41 | "args": { 42 | "file": "Packages/User/Sort Lines (Numerically) Reverse.sublime-macro" 43 | } 44 | } 45 | ] 46 | ``` 47 | 48 | You will now have a command called `Sort Lines (Numerically) Reverse` in your command palette. If you want to bind this command to a keyboard shortcut, add something like the following to your default user keymap file (available at `Preferences` > `Key Bindings – User`: 49 | 50 | ``` 51 | { 52 | "keys": ["ctrl+shift+f5"], 53 | "command": "run_macro_file", 54 | "args": { 55 | "file": "Packages/User/Sort Lines (Numerically) Reverse.sublime-macro" 56 | } 57 | } 58 | ``` 59 | 60 | Credits 61 | ------- 62 | Thanks to the following contributors: 63 | 64 | * Curtis Gibby – [@curtisgibby](https://github.com/curtisgibby) 65 | * David Jack Wange Olrik – [@davidolrik](https://github.com/davidolrik) 66 | * Frank Tan – [@tansongyang](https://github.com/tansongyang) 67 | -------------------------------------------------------------------------------- /SortNumericallyCommand.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | import os 4 | import sys 5 | 6 | import sublime 7 | import sublime_plugin 8 | 9 | sys.path.append(os.path.dirname(sys.executable)) 10 | 11 | try: 12 | # This import method works in Sublime Text 2. 13 | from sort_numerically import sort_lines 14 | except ImportError: 15 | # While this works in Sublime Text 3. 16 | from .sort_numerically import sort_lines 17 | 18 | 19 | class SortNumericallyCommand(sublime_plugin.TextCommand): 20 | 21 | def run(self, edit): 22 | 23 | regions = self.view.sel() 24 | 25 | if len(regions) == 1 and regions[0].empty(): 26 | # Selection is empty, use the entire buffer. 27 | regions = [sublime.Region(0, self.view.size())] 28 | 29 | for region in regions: 30 | input_lines = [self.view.substr(r) for r in self.view.lines(region)] 31 | sorted_lines = sort_lines(input_lines) 32 | 33 | # Fetch the actual line ending characters used, assuming the same is 34 | # used througout the entire region. 35 | first_line = self.view.substr(self.view.full_line(0)) 36 | stripped_line = first_line.rstrip() 37 | line_ending_length = len(first_line) - len(stripped_line) 38 | line_ending = first_line[len(first_line) - line_ending_length:] 39 | 40 | output = line_ending.join(sorted_lines) 41 | 42 | # If the end of the region had a line ending character, we re-add it here 43 | if self.view.substr(region).endswith(line_ending): 44 | output += line_ending 45 | 46 | self.view.replace(edit, region, output) 47 | -------------------------------------------------------------------------------- /dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "*": { 3 | "*": [ 4 | "natsort" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /sort_numerically.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ''' 4 | The sort_numerically() function lives in a separate file so it can be tested by tests.py 5 | ''' 6 | 7 | from __future__ import unicode_literals 8 | 9 | # from natsort import realsorted 10 | from natsort import realsorted 11 | 12 | 13 | def sort_lines(input_lines): 14 | return realsorted(input_lines) 15 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # encoding: utf-8 3 | 4 | '''Run some basic tests on various lists of input lines.''' 5 | 6 | from __future__ import unicode_literals 7 | 8 | if __name__ == '__main__': 9 | import unittest 10 | from sort_numerically import sort_lines 11 | 12 | class TestSortNumerically(unittest.TestCase): 13 | 14 | def test_simple_noop(self): 15 | input_lines = [ 16 | '1', 17 | '2', 18 | '3', 19 | ] 20 | expected_output_lines = [ 21 | '1', 22 | '2', 23 | '3', 24 | ] 25 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 26 | 27 | def test_simple_noop_with_alpha(self): 28 | input_lines = [ 29 | '1 a', 30 | '2 b', 31 | '3 c', 32 | ] 33 | expected_output_lines = [ 34 | '1 a', 35 | '2 b', 36 | '3 c', 37 | ] 38 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 39 | 40 | def test_simple_sort(self): 41 | input_lines = [ 42 | '1', 43 | '3', 44 | '2', 45 | ] 46 | expected_output_lines = [ 47 | '1', 48 | '2', 49 | '3', 50 | ] 51 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 52 | 53 | def test_simple_sort_with_alpha(self): 54 | input_lines = [ 55 | '1 a', 56 | '3 b', 57 | '2 c', 58 | ] 59 | expected_output_lines = [ 60 | '1 a', 61 | '2 c', 62 | '3 b', 63 | ] 64 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 65 | 66 | def test_sort_with_decimals(self): 67 | input_lines = [ 68 | '1.5', 69 | '1', 70 | '2.5', 71 | '2', 72 | ] 73 | expected_output_lines = [ 74 | '1', 75 | '1.5', 76 | '2', 77 | '2.5', 78 | ] 79 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 80 | 81 | def test_sort_with_multiple_int_groups(self): 82 | input_lines = [ 83 | '1 1', 84 | '3 2', 85 | '2 3', 86 | ] 87 | expected_output_lines = [ 88 | '1 1', 89 | '2 3', 90 | '3 2', 91 | ] 92 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 93 | 94 | def test_sort_with_negatives(self): 95 | input_lines = [ 96 | '1', 97 | '-2', 98 | '2', 99 | '-1', 100 | '3', 101 | ] 102 | expected_output_lines = [ 103 | '-2', 104 | '-1', 105 | '1', 106 | '2', 107 | '3', 108 | ] 109 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 110 | 111 | def test_sort_with_negative_decimals(self): 112 | input_lines = [ 113 | '2', 114 | '1.2', 115 | '1', 116 | '0', 117 | '-1', 118 | '-1.2', 119 | '-2', 120 | ] 121 | expected_output_lines = [ 122 | '-2', 123 | '-1.2', 124 | '-1', 125 | '0', 126 | '1', 127 | '1.2', 128 | '2', 129 | ] 130 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 131 | 132 | def test_sort_with_negatives_and_alpha(self): 133 | input_lines = [ 134 | '1 a', 135 | '-2 b', 136 | '2 c', 137 | '-1 d', 138 | '3 e', 139 | ] 140 | expected_output_lines = [ 141 | '-2 b', 142 | '-1 d', 143 | '1 a', 144 | '2 c', 145 | '3 e', 146 | ] 147 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 148 | 149 | def test_sort_with_alphanumeric_partial_formatting(self): 150 | input_lines = [ 151 | '2 a', 152 | '1 b2', 153 | '1 b.', 154 | ] 155 | expected_output_lines = [ 156 | '1 b2', 157 | '1 b.', 158 | '2 a', 159 | ] 160 | self.assertEqual(sort_lines(input_lines), expected_output_lines) 161 | 162 | unittest.main(argv=['TestSortNumerically']) 163 | --------------------------------------------------------------------------------