├── .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 | 
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
--------------------------------------------------------------------------------