├── .gitignore ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── LICENSE ├── Main.sublime-menu ├── README.md ├── StickySearch.py ├── StickySearch.sublime-settings └── example.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | 2 | // 3 | [ 4 | { "keys": ["ctrl+8"], "command": "stickysearch", "args": {"op" : "set"} }, 5 | { "keys": ["ctrl+shift+8"], "command": "stickysearch", "args": {"op" : "add"} }, 6 | { "keys": ["ctrl+alt+8"], "command": "stickysearch", "args": {"op" : "clear"} } 7 | ] -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | 2 | // 3 | [ 4 | { "keys": ["super+8"], "command": "stickysearch", "args": {"op" : "set"} }, 5 | { "keys": ["super+shift+8"], "command": "stickysearch", "args": {"op" : "add"} }, 6 | { "keys": ["super+alt+8"], "command": "stickysearch", "args": {"op" : "clear"} } 7 | ] -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | 2 | // 3 | [ 4 | { "keys": ["ctrl+8"], "command": "stickysearch", "args": {"op" : "set"} }, 5 | { "keys": ["ctrl+shift+8"], "command": "stickysearch", "args": {"op" : "add"} }, 6 | { "keys": ["ctrl+alt+8"], "command": "stickysearch", "args": {"op" : "clear"} } 7 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ofer Affias 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "mnemonic": "n", 5 | "id": "preferences", 6 | "children": 7 | [ 8 | { 9 | "caption": "Package Settings", 10 | "mnemonic": "P", 11 | "id": "package-settings", 12 | "children": 13 | [ 14 | { 15 | "caption": "Sticky Search", 16 | "children": 17 | [ 18 | { 19 | "command": "open_file", 20 | "args": {"file": "${packages}/StickySearch/StickySearch.sublime-settings"}, 21 | "caption": "Settings – Default" 22 | }, 23 | { 24 | "command": "open_file", 25 | "args": {"file": "${packages}/User/StickySearch.sublime-settings"}, 26 | "caption": "Settings – User" 27 | }, 28 | { "caption": "-" }, 29 | { 30 | "command": "open_file", 31 | "args": { 32 | "file": "${packages}/StickySearch/Default (Windows).sublime-keymap", 33 | "platform": "Windows" 34 | }, 35 | "caption": "Key Bindings – Default" 36 | }, 37 | { 38 | "command": "open_file", 39 | "args": { 40 | "file": "${packages}/StickySearch/Default (OSX).sublime-keymap", 41 | "platform": "OSX" 42 | }, 43 | "caption": "Key Bindings – Default" 44 | }, 45 | { 46 | "command": "open_file", 47 | "args": { 48 | "file": "${packages}/StickySearch/Default (Linux).sublime-keymap", 49 | "platform": "Linux" 50 | }, 51 | "caption": "Key Bindings – Default" 52 | }, 53 | { 54 | "command": "open_file", 55 | "args": { 56 | "file": "${packages}/User/Default (Windows).sublime-keymap", 57 | "platform": "Windows" 58 | }, 59 | "caption": "Key Bindings – User" 60 | }, 61 | { 62 | "command": "open_file", 63 | "args": { 64 | "file": "${packages}/User/Default (OSX).sublime-keymap", 65 | "platform": "OSX" 66 | }, 67 | "caption": "Key Bindings – User" 68 | }, 69 | { 70 | "command": "open_file", 71 | "args": { 72 | "file": "${packages}/User/Default (Linux).sublime-keymap", 73 | "platform": "Linux" 74 | }, 75 | "caption": "Key Bindings – User" 76 | }, 77 | ] 78 | } 79 | ] 80 | } 81 | ] 82 | } 83 | ] 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StickySearch 2 | 3 | A Sublime Text plugin - persistently highlighting selected text. This plugin is designed 4 | to elevate your text highlighting experience, drawing inspiration from VIM's keyword search functionality. 5 | 6 | Compatible with Sublime Text 3 and 4. 7 | 8 | ![](example.png) 9 | 10 | ## Install 11 | 12 | Search for StickySearch in [Package Control](https://packagecontrol.io/packages/StickySearch). 13 | 14 | ## How It Works 15 | 16 | To highlight text under the cursor: 17 | 18 | - macOS: `Command` + `8` 19 | - Windows/Linux: `Ctrl` + `8` 20 | 21 | To highlight more text (while retaining previous highlights): 22 | 23 | - macOS: `Shift` + `Command` + `8` 24 | - Windows/Linux: `Shift` + `Ctrl` + `8` 25 | 26 | To clear all highlighted text: 27 | 28 | - macOS: `Alt` + `Command` + `8` 29 | - Windows/Linux: `Alt` + `Ctrl` + `8` 30 | 31 | ## Customization 32 | 33 | You can change the following settings: 34 | 35 | | Parameter | Values | Description | 36 | |-----------|-------------------------------------|-----------------------------------------------------| 37 | | `icon` | `dot`, `circle`, `bookmark`, `cross`| When provided, the named icon will be displayed in the gutter | 38 | | `fill` | `true`, `false` | When set to `true`, it adds a background color to marked text | 39 | | `outline` | `true`, `false` | When set to `true`, it encloses marked text with a colored frame | 40 | | `rainbow` | `true`, `false` | When set to `true`, different colors are used for each added selection | 41 | -------------------------------------------------------------------------------- /StickySearch.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sublime, sublime_plugin 3 | 4 | #view.run_command("stickysearch") 5 | class StickysearchCommand(sublime_plugin.TextCommand): 6 | _scopes = [ 7 | "region.yellowish", 8 | "region.bluish", 9 | "region.redish", 10 | "region.orangish", 11 | "region.greenish", 12 | "region.cyanish", 13 | "region.purplish", 14 | "region.pinkish", 15 | ] 16 | _keys = [] 17 | _keybase = "StickySearch" 18 | 19 | def run(self, edit, op): 20 | # keep sticky per window (each window has its own set) 21 | if 'add' in op: 22 | self.op_add() 23 | if 'clear' in op: 24 | self.op_clear() 25 | if 'set' in op: 26 | self.op_clear() 27 | self.op_add() 28 | 29 | def op_add(self): 30 | selected_region = self.view.sel()[0] 31 | selected_word = self.selection_under_cursor(selected_region) 32 | key = self.marker_key(selected_word) 33 | regions = self.view.find_all(selected_word) 34 | # Mark new selections, or jump to next resion on exsiting marks 35 | if key not in self._keys: 36 | self.mark(key, regions) 37 | self._keys.append(key) 38 | else: 39 | self.jump_to_next_match(selected_region, regions) 40 | 41 | def op_clear(self): 42 | selected_region = self.view.sel()[0] 43 | selected_word = self.selection_under_cursor(selected_region) 44 | marker_key = self.marker_key(selected_word) 45 | 46 | preserve_keys = [] 47 | for key in self._keys: 48 | if key != marker_key: 49 | self.view.erase_regions(key) 50 | else: 51 | preserve_keys.append(marker_key) 52 | 53 | self._keys = preserve_keys 54 | 55 | def marker_key(self, uid): 56 | return self._keybase + uid 57 | 58 | def jump_to_next_match(self, sel, regions): 59 | the_word_region = self.region_under_cursor(sel) 60 | for pos in regions: 61 | if pos.a > the_word_region.a: 62 | break 63 | else: 64 | # cycle back to first match 65 | pos = regions[0] 66 | 67 | pos.b = pos.a 68 | self.view.sel().clear() 69 | self.view.sel().add(pos) 70 | self.view.show(pos) 71 | 72 | def mark(self, key, regions): 73 | settings = sublime.load_settings("StickySearch.sublime-settings") 74 | flags = sublime.PERSISTENT 75 | if not settings.get("fill", False): 76 | flags |= sublime.DRAW_NO_FILL 77 | if not settings.get("outline", True): 78 | flags |= sublime.DRAW_NO_OUTLINE 79 | 80 | # optional icon name, if given, will draw the named icons in the gutter next to each region. 81 | # The icon will be tinted using the color associated with the scope. 82 | # Valid icon names are dot, circle, bookmark and cross 83 | icon_name = settings.get("icon", "dot") 84 | 85 | # optional string used to source a color to draw the regions in. The scope is matched 86 | # against the color scheme. 87 | next_marker_index = len(self._keys) 88 | num_of_available_scopes = len(self._scopes) 89 | if settings.get("rainbow", True): 90 | scope = self._scopes[next_marker_index % num_of_available_scopes] 91 | else: 92 | scope = "region.yellowish" 93 | 94 | self.view.add_regions(key, regions, scope, icon_name, flags) 95 | 96 | def selection_under_cursor(self, sel): 97 | # if there is visual selection, just use it to find all without word boundaries 98 | # if no selection then expand to word under cursor and find all using word boundaries 99 | has_selection = sel.a != sel.b 100 | if has_selection: 101 | the_word_region = sel 102 | selected_txt = self.view.substr(the_word_region) 103 | # support special charecters 104 | selected_word = re.escape(selected_txt) 105 | else: 106 | the_word_region = self.view.word(sel) 107 | selected_word = "\\b" + self.view.substr(the_word_region) + "\\b" 108 | 109 | return selected_word 110 | 111 | def region_under_cursor(self, sel): 112 | # if there is visual selection, just use it to find all without word boundaries 113 | # if no selection then expand to word under cursor and find all using word boundaries 114 | has_selection = sel.a != sel.b 115 | if has_selection: 116 | the_word_region = sel 117 | else: 118 | the_word_region = self.view.word(sel) 119 | 120 | return the_word_region 121 | -------------------------------------------------------------------------------- /StickySearch.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // Icon name. If given, will draw the named icons in the gutter. 3 | // Valid icon names are dot, circle, bookmark and cross. 4 | // The icon name may also be a full package relative path, 5 | // such as Packages/Theme - Default/dot.png. 6 | "icon": "dot", 7 | // Style of search highlighting 8 | "fill": false, 9 | "outline": true, 10 | // Use different colors for additional selections 11 | "rainbow": true 12 | } 13 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-zz/StickySearch/0d3f450b2b3c657c406388b84590416df08a0156/example.png --------------------------------------------------------------------------------