├── .gitattributes ├── .gitignore ├── messages ├── 2.0.1.md ├── 2.0.2.md ├── 2.0.0.md ├── 2.0.3.md ├── 3.1.3.md ├── 3.1.4.md ├── 3.1.2.md ├── 3.1.0.md ├── install.md └── 3.0.0.md ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── messages.json ├── Filter Lines.sublime-settings ├── CONTRIBUTORS.md ├── LICENSE ├── Default.sublime-commands ├── README.md ├── Main.sublime-menu └── filter.py /.gitattributes: -------------------------------------------------------------------------------- 1 | filter_lines_demo.gif export-ignore 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.sublime-project 3 | *.sublime-workspace 4 | -------------------------------------------------------------------------------- /messages/2.0.1.md: -------------------------------------------------------------------------------- 1 | 2.0.1 (2014-12-27) 2 | ------------------ 3 | 4 | - Added changelog 5 | -------------------------------------------------------------------------------- /messages/2.0.2.md: -------------------------------------------------------------------------------- 1 | 2.0.3 (2014-12-28) 2 | ------------------ 3 | 4 | - Renamed GitHub repository as sublimetext-filterlines 5 | -------------------------------------------------------------------------------- /messages/2.0.0.md: -------------------------------------------------------------------------------- 1 | 2.0.0 (2014-12-26) 2 | ------------------ 3 | 4 | - Moved support for Sublime Text 2 into a branch 5 | - Cleaned up the documentation 6 | -------------------------------------------------------------------------------- /messages/2.0.3.md: -------------------------------------------------------------------------------- 1 | 2.0.3 (2014-12-28) 2 | ------------------ 3 | 4 | - Renamed GitHub repository as sublime-filterlines 5 | - Renamed message files to get them to show on update 6 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+k", "ctrl+r"], "command": "prompt_filter_to_lines", "args": { "search_type": "regex", "invert_search": false } }, 3 | { "keys": ["ctrl+k", "ctrl+s"], "command": "prompt_filter_to_lines", "args": { "search_type": "string", "invert_search": false } } 4 | ] 5 | -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["super+k", "super+r"], "command": "prompt_filter_to_lines", "args": { "search_type": "regex", "invert_search": false } }, 3 | { "keys": ["super+k", "super+s"], "command": "prompt_filter_to_lines", "args": { "search_type": "string", "invert_search": false } } 4 | ] 5 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+k", "ctrl+r"], "command": "prompt_filter_to_lines", "args": { "search_type": "regex", "invert_search": false } }, 3 | { "keys": ["ctrl+k", "ctrl+s"], "command": "prompt_filter_to_lines", "args": { "search_type": "string", "invert_search": false } } 4 | ] 5 | -------------------------------------------------------------------------------- /messages/3.1.3.md: -------------------------------------------------------------------------------- 1 | #3.1.3 May 24, 2019 2 | 3 | This release is for Sublime Text 3 users. I have separate versions of the plugin for Sublime Text 2 and 3, and the version for Sublime Text 2 remains unchanged for now. 4 | 5 | ##Changes 6 | 7 | - Cleaned up code formatting ala PEP 8 8 | - Menu commands now appear in title case 9 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.md", 3 | "2.0.0": "messages/2.0.0.md", 4 | "2.0.1": "messages/2.0.1.md", 5 | "2.0.2": "messages/2.0.2.md", 6 | "2.0.3": "messages/2.0.3.md", 7 | "3.0.0": "messages/3.0.0.md", 8 | "3.1.0": "messages/3.1.0.md", 9 | "3.1.2": "messages/3.1.2.md", 10 | "3.1.3": "messages/3.1.3.md", 11 | "3.1.4": "messages/3.1.4.md" 12 | } 13 | -------------------------------------------------------------------------------- /messages/3.1.4.md: -------------------------------------------------------------------------------- 1 | #3.1.4 August 8, 2019 2 | 3 | This release is for Sublime Text 3 users. I have separate versions of the plugin for Sublime Text 2 and 3, and the version for Sublime Text 2 remains unchanged for now. 4 | 5 | ##Changes 6 | 7 | - Merged fold.py into filter.py as suggested by FichteFoll to resolve issue #53, which sometimes caused Filter Lines menu items to appear disabled in ST 3. 8 | -------------------------------------------------------------------------------- /messages/3.1.2.md: -------------------------------------------------------------------------------- 1 | #3.1.2 May 20, 2018 2 | 3 | This release is for Sublime Text 3 users. I have separate versions of the plugin for Sublime Text 2 and 3, and the version for Sublime Text 2 remains unchanged for now. 4 | 5 | ##Changes 6 | 7 | - Added install instructions 8 | 9 | ##Regular Expression Syntax 10 | Earlier releases used Python regular expression syntax. This release uses Sublime Text search, which implements the Perl regular expression syntax from the Boost library. For more on the Boost Perl syntax, see: 11 | 12 | http://www.boost.org/doc/libs/1_44_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html 13 | -------------------------------------------------------------------------------- /Filter Lines.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // If true, string searches are case-sensitive 3 | "case_sensitive_string_search": false, 4 | 5 | // If true, regular expression matching is case-sensitive 6 | "case_sensitive_regex_search": true, 7 | 8 | // If true, your latest search string or regex will be saved, 9 | // and then restored for your next search. If false, the search 10 | // string or regex will be blank 11 | "preserve_search": true, 12 | 13 | // Show source file line numbers in results 14 | "line_numbers": false, 15 | 16 | // Create new tab for filter results 17 | "create_new_tab": true, 18 | 19 | // max visible length of the new tab title. It includes the prefix appended with the search term. 20 | "new_tab_title_max_length" : 25 21 | } 22 | -------------------------------------------------------------------------------- /messages/3.1.0.md: -------------------------------------------------------------------------------- 1 | 3.1.0 April 10, 2017 2 | ---------------------- 3 | 4 | This release is for Sublime Text 3 users. I have separate versions of the plugin for Sublime Text 2 and 3, and the version for Sublime Text 2 remains unchanged for now. 5 | 6 | Changes 7 | ------- 8 | - Folding is faster 9 | - Filtering in place is back! 10 | - New menus on the Edit > Lines and Edit > Code Folding menus, and in package manager 11 | 12 | Regular Expression Syntax 13 | ------------------------- 14 | Earlier releases used Python regular expression syntax. This release uses Sublime Text search, which implements the Perl regular expression syntax from the Boost library. For more on the Boost Perl syntax, see: 15 | 16 | http://www.boost.org/doc/libs/1_44_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html 17 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Filter Lines is maintained by 2 | 3 | - David Peckham (davidpeckham) 4 | 5 | and is based on a Sublime Text [plugin tutorial by Daniel Beck](http://superuser.com/questions/452189/how-can-i-filter-a-file-for-lines-containing-a-string-in-sublime-text-2). If you want to write a plugin, Daniel's tutorial is a good place to start. 6 | 7 | Contributors 8 | ------------ 9 | - Nathan Kot (nathankot) for suggesting folding filtered lines 10 | - Marc Schlaich (schlamar) for adding support for custom separators (Sublime Text 2 only) 11 | - grayhare for fixing filtering in place (Sublime Text 2 only) 12 | - skafian and purplelizard for suggesting source line numbers (Sublime Text 3 only) 13 | - timothyaaron for fixing a code folding bug and defaulting search to the word at cursor 14 | - poma for performance improvements, filtering in place, and adding commands for filtering in place and inverse filters 15 | - uglycoyote, reagle, and FichteFoll for their persistence and insight with issue #53 16 | -------------------------------------------------------------------------------- /messages/install.md: -------------------------------------------------------------------------------- 1 | Quickly find lines that match a string or regular expression. 2 | 3 | To filter your file to lines that contain a string or match a regex: 4 | 5 | Edit > Line > Include Lines With Regex (cmd + K cmd + R) 6 | Edit > Line > Include Lines With String (cmd + K cmd + S) 7 | 8 | To filter your file and remove lines that contain a string or match a regex: 9 | 10 | Edit > Line > Exclude Lines With Regex 11 | Edit > Line > Exclude Lines With String 12 | 13 | To fold your file to lines that contain a string or match a regex: 14 | 15 | Edit > Code Folding > Fold With Regex 16 | Edit > Code Folding > Fold With String 17 | 18 | To fold your file to lines that don't contain a string or match a regex: 19 | 20 | Edit > Code Folding > Fold Excluding Regex 21 | Edit > Code Folding > Fold Excluding String 22 | 23 | On Windows and Linux, use ctrl instead of cmd. 24 | 25 | 26 | For feedback, bugs, and source code: 27 | 28 | https://github.com/davidpeckham/sublimetext-filterlines 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2019 David Peckham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "Filter Lines: Include Lines With Regex", "command": "prompt_filter_to_lines", "args": { "search_type": "regex", "invert_search": false } }, 3 | { "caption": "Filter Lines: Include Lines With String", "command": "prompt_filter_to_lines", "args": { "search_type": "string", "invert_search": false } }, 4 | { "caption": "Filter Lines: Exclude Lines With Regex", "command": "prompt_filter_to_lines", "args": { "search_type": "regex", "invert_search": true } }, 5 | { "caption": "Filter Lines: Exclude Lines With String", "command": "prompt_filter_to_lines", "args": { "search_type": "string", "invert_search": true } }, 6 | { "caption": "Filter Lines: Fold Excluding Regex", "command": "prompt_fold_to_lines", "args": { "search_type": "regex", "invert_search": false } }, 7 | { "caption": "Filter Lines: Fold Excluding String", "command": "prompt_fold_to_lines", "args": { "search_type": "string", "invert_search": false } }, 8 | { "caption": "Filter Lines: Fold With Regex", "command": "prompt_fold_to_lines", "args": { "search_type": "regex", "invert_search": true } }, 9 | { "caption": "Filter Lines: Fold With String", "command": "prompt_fold_to_lines", "args": { "search_type": "string", "invert_search": true } } 10 | ] 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Filter Lines 2 | 3 | Quickly find all lines matching a string or regular expression in Sublime Text. 4 | 5 | ## How to Filter 6 | 7 | On the Edit > Line menu: 8 | 9 | * Include Lines With Regex ⌘+K ⌘+R 10 | * Include Lines With String ⌘+K ⌘+S 11 | * Exclude Lines With Regex 12 | * Exclude Lines With String 13 | 14 | On the Edit > Code Folding menu: 15 | 16 | * Fold Excluding Regex 17 | * Fold Excluding String 18 | * Fold With Regex 19 | * Fold With String 20 | 21 | On Windows and Linux, use ctrl instead of . 22 | 23 | ## How to Install 24 | 25 | ### Install with Package Control 26 | 1. Install [Sublime Package Control](http://wbond.net/sublime_packages/package_control/installation) 27 | 2. Open the command pallette ⌘+shift+P 28 | 3. Select Install Package 29 | 3. Select Filter Lines 30 | 31 | ### Install with Git 32 | 1. Open the Sublime Text package folder (see Sublime Text > Preferences > Browse Packages...) 33 | 2. Clone this repo 34 | 3. Restart Sublime Text 35 | 36 | ## Demo 37 | 38 | ![Filter Lines Demo](https://github.com/davidpeckham/sublime-filterlines/blob/master/filter_lines_demo.gif) 39 | 40 | ## Thanks 41 | 42 | Filter Lines is based on a Sublime Text [plugin tutorial by Daniel Beck](http://superuser.com/questions/452189/how-can-i-filter-a-file-for-lines-containing-a-string-in-sublime-text-2). If you want to write a plugin, Daniel's tutorial is a good place to start. 43 | -------------------------------------------------------------------------------- /messages/3.0.0.md: -------------------------------------------------------------------------------- 1 | 3.0.0 January 4, 2015 2 | ---------------------- 3 | It's been almost two years since I created this plugin, based on a tutorial by Daniel Beck. At the time, I had moved from TextMate to Sublime Text, and missed TextMate's Filter Lines feature. So I adapted Daniel's tutorial and used the plugin almost every day. With help from others, I added features like folding, filtering for non-matching lines, filtering in place, and custom line separators. But it wasn't very fast on large files, and with the new features it was getting harder to extend. 4 | 5 | For this release, I made Filter Lines faster, especially for very large files, and refactored and simplified it so that it will be easier for others to extend. I refocused on the plugin's most useful features, and rewrote it to use the native Sublime Text 3 API where possible instead of Python modules. For example, I used the Sublime Text 3 search API instead of the Python regular expression module. 6 | 7 | Unfortunately, I dropped two features -- filtering in place and custom line separators. I dropped filtering in place because I prefer not to modify the source buffer, and I dropped custom line separators to simplify the plugin. 8 | 9 | This release is for Sublime Text 3 users. I have separate versions of the plugin for Sublime Text 2 and 3, and the version for Sublime Text 2 remains unchanged for now. 10 | 11 | Changes 12 | ------- 13 | - Faster for large files 14 | - Now uses Perl regular expression syntax instead of Python regular expression syntax 15 | - Now you can show source line numbers (see the new line_numbers preference, off by default) 16 | - Now always filters to a new buffer (no longer supports filtering in place) 17 | - No longer supports custom line separators 18 | - Refactored commands to make this plugin easier for others to extend 19 | 20 | Regular Expression Syntax 21 | ------------------------- 22 | Earlier releases used Python regular expression syntax. This release uses Sublime Text search, which implements the Perl regular expression syntax from the Boost library. For more on the Boost Perl syntax, see: 23 | 24 | http://www.boost.org/doc/libs/1_44_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html 25 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "edit", 4 | "children": 5 | [ 6 | { 7 | "id": "line", 8 | "children": 9 | [ 10 | { "caption": "-" }, 11 | { "caption": "Include Lines With Regex", "command": "prompt_filter_to_lines", "args": { "search_type": "regex", "invert_search": false } }, 12 | { "caption": "Include Lines With String", "command": "prompt_filter_to_lines", "args": { "search_type": "string", "invert_search": false } }, 13 | { "caption": "Exclude Lines With Regex", "command": "prompt_filter_to_lines", "args": { "search_type": "regex", "invert_search": true } }, 14 | { "caption": "Exclude Lines With String", "command": "prompt_filter_to_lines", "args": { "search_type": "string", "invert_search": true } } 15 | ] 16 | }, 17 | { 18 | "id": "fold", 19 | "children": 20 | [ 21 | { "caption": "-" }, 22 | { "caption": "Fold Excluding Regex", "command": "prompt_fold_to_lines", "args": { "search_type": "regex", "invert_search": false } }, 23 | { "caption": "Fold Excluding String", "command": "prompt_fold_to_lines", "args": { "search_type": "string", "invert_search": false } }, 24 | { "caption": "Fold With Regex", "command": "prompt_fold_to_lines", "args": { "search_type": "regex", "invert_search": true } }, 25 | { "caption": "Fold With String", "command": "prompt_fold_to_lines", "args": { "search_type": "string", "invert_search": true } } 26 | ] 27 | } 28 | ] 29 | }, 30 | 31 | { 32 | "caption": "Preferences", 33 | "mnemonic": "n", 34 | "id": "preferences", 35 | "children": 36 | [ 37 | { 38 | "caption": "Package Settings", 39 | "mnemonic": "P", 40 | "id": "package-settings", 41 | "children": 42 | [ 43 | { 44 | "caption": "Filter Lines", 45 | "children": 46 | [ 47 | { 48 | "command": "open_file", 49 | "args": {"file": "${packages}/Filter Lines/Filter Lines.sublime-settings"}, 50 | "caption": "Settings – Default" 51 | }, 52 | { 53 | "command": "open_file", 54 | "args": {"file": "${packages}/User/Filter Lines.sublime-settings"}, 55 | "caption": "Settings – User" 56 | } 57 | ] 58 | } 59 | ] 60 | } 61 | ] 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /filter.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import sublime 3 | import sublime_plugin 4 | 5 | settings_path = 'Filter Lines.sublime-settings' 6 | 7 | 8 | class PromptFilterToLinesCommand(sublime_plugin.WindowCommand): 9 | 10 | def run(self, search_type='string', invert_search=False): 11 | self._run(search_type, 'filter_to_lines', 'Filter', invert_search) 12 | 13 | def _run(self, search_type, filter_command, filter_verb, invert_search): 14 | self.load_settings() 15 | self.filter_command = filter_command 16 | self.search_type = search_type 17 | self.invert_search = invert_search 18 | if search_type == 'string': 19 | prompt = "%s to lines %s: " % ( 20 | filter_verb, 21 | 'not containing string' if self.invert_search else 'containing string') 22 | else: 23 | prompt = "%s to lines %s: " % ( 24 | filter_verb, 25 | 'not matching regex' if self.invert_search else 'matching regex') 26 | if not self.search_text: 27 | view = self.window.active_view() 28 | first = view.sel()[0] # first region (or point) 29 | region = first if first.size() else view.word(first.begin()) 30 | word = view.substr(region) 31 | self.search_text = word 32 | sublime.active_window().show_input_panel( 33 | prompt, self.search_text, self.on_search_text_entered, None, None) 34 | 35 | def on_search_text_entered(self, search_text): 36 | self.search_text = search_text 37 | self.save_settings() 38 | if self.window.active_view(): 39 | self.window.active_view().run_command( 40 | self.filter_command, { 41 | "needle": self.search_text, 42 | "search_type": self.search_type, 43 | "invert_search": self.invert_search 44 | } 45 | ) 46 | 47 | def load_settings(self): 48 | self.settings = sublime.load_settings(settings_path) 49 | self.search_text = "" 50 | if self.settings.get('preserve_search', True): 51 | self.search_text = self.settings.get('latest_search', '') 52 | 53 | def save_settings(self): 54 | if self.settings.get('preserve_search', True): 55 | self.settings.set('latest_search', self.search_text) 56 | 57 | 58 | class FilterToLinesCommand(sublime_plugin.TextCommand): 59 | 60 | def run(self, edit, needle, search_type, invert_search): 61 | settings = sublime.load_settings(settings_path) 62 | flags = self.get_search_flags(search_type, settings) 63 | lines = itertools.groupby( 64 | self.view.find_all(needle, flags), self.view.line) 65 | lines = [l for l, _ in lines] 66 | self.line_numbers = settings.get('line_numbers', False) 67 | # exclude title prefix in title name length calculation 68 | self.title_length = settings.get('new_tab_title_max_length', 20) - 16 69 | self.new_tab = settings.get('create_new_tab', True) 70 | self.needle = needle 71 | self.invert_search = invert_search ^ (not self.new_tab) 72 | self.show_filtered_lines(edit, lines) 73 | 74 | def get_search_flags(self, search_type, settings): 75 | flags = 0 76 | if search_type == 'string': 77 | flags = sublime.LITERAL 78 | if not settings.get('case_sensitive_string_search', False): 79 | flags = flags | sublime.IGNORECASE 80 | elif search_type == 'regex': 81 | if not settings.get('case_sensitive_regex_search', False): 82 | flags = sublime.IGNORECASE 83 | return flags 84 | 85 | def show_filtered_lines(self, edit, lines): 86 | if self.invert_search: 87 | filtered_line_numbers = [ 88 | self.view.rowcol(line.begin())[0] for line in lines 89 | ] 90 | lines = self.view.lines(sublime.Region(0, self.view.size())) 91 | for line_number in reversed(filtered_line_numbers): 92 | del lines[line_number] 93 | 94 | if self.new_tab: 95 | text = '\n'.join( 96 | [self.prepare_output_line(l) for l in lines] 97 | ) 98 | self.create_new_tab(text, self.needle) 99 | else: 100 | for line in reversed(lines): 101 | self.view.erase(edit, self.view.full_line(line)) 102 | 103 | def create_new_tab(self, text, title=''): 104 | results_view = self.view.window().new_file() 105 | title = '%s...' % (title[:self.title_length]) if len( 106 | title) > self.title_length else title 107 | results_view.set_name('Filter Results: %s' % (title)) 108 | results_view.set_scratch(True) 109 | results_view.settings().set( 110 | 'word_wrap', self.view.settings().get('word_wrap')) 111 | results_view.run_command( 112 | 'append', 113 | {'characters': text, 'force': True, 'scroll_to_end': False} 114 | ) 115 | results_view.set_syntax_file(self.view.settings().get('syntax')) 116 | 117 | def prepare_output_line(self, line): 118 | if self.line_numbers and not self.invert_search: 119 | line_number = self.view.rowcol(line.begin())[0] 120 | return '%5d: %s' % (line_number, self.view.substr(line)) 121 | else: 122 | return self.view.substr(line) 123 | 124 | 125 | class PromptFoldToLinesCommand(PromptFilterToLinesCommand): 126 | 127 | def run(self, search_type='string', invert_search=False): 128 | self._run(search_type, "fold_to_lines", "Fold", invert_search) 129 | 130 | 131 | class FoldToLinesCommand(FilterToLinesCommand): 132 | 133 | def show_filtered_lines(self, edit, lines): 134 | source_lines = self.view.lines(sublime.Region(0, self.view.size())) 135 | filtered_line_numbers = { 136 | self.view.rowcol(line.begin())[0] for line in lines 137 | } 138 | regions = [] 139 | region = None 140 | for line in source_lines: 141 | matched = ( 142 | self.view.rowcol( 143 | line.begin() 144 | )[0] in filtered_line_numbers) ^ self.invert_search 145 | if matched: 146 | if region: 147 | regions.append(region) 148 | region = None 149 | else: 150 | if region: 151 | region = region.cover(line) 152 | else: 153 | region = sublime.Region(line.begin(), line.end()) 154 | if region: 155 | regions.append(region) 156 | if regions: 157 | self.view.fold(regions) 158 | --------------------------------------------------------------------------------