├── .gitignore
├── Default.sublime-keymap
├── LICENSE
├── Main.sublime-menu
├── README.md
├── VulData.sublime-settings
├── VulHint.py
└── plugin.sublime-settings
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 | .DS_Store
91 |
--------------------------------------------------------------------------------
/Default.sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | { "keys": ["alt+space"], "command": "goto_next" }
3 | ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 5alt
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 | "id" : "VulHint",
4 | "caption": "VulHint",
5 | "children":
6 | [
7 | {
8 | "caption" : "enable",
9 | "command" : "enable"
10 | },
11 | {
12 | "caption" : "disable",
13 | "command" : "disable"
14 | },
15 | {
16 | "caption" : "clear",
17 | "command" : "clear"
18 | }
19 | ]
20 | }
21 | ]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VulHint
2 |
3 | VulHint 是一款 sublime text 3 的代码审计插件。
4 |
5 | 插件包含一些预置的正则规则和提示的内容,在当前文本中匹配到代码的时候,会加框显示,鼠标移动到危险函数的时候,会展示提示信息。
6 |
7 | 在代码审计的时候,本插件可以针对相关知识点做出提醒。
8 |
9 | ## 漏洞提示
10 |
11 | 在编辑器行号前有标记符号,对规则匹配到的部分用方框标出。鼠标移动到该行会有相应提示。如果一行中有多个标记,则所有的提示信息都会出现,鼠标移动到某一个上则只会出现该规则的提示。
12 |
13 | `alt+space` 可以切换到下一个提示点。
14 |
15 | ## 数据扩展
16 |
17 | `VulData.sublime-settings` 中存放了相关的规则数据,可以方便的自行添加。
18 |
19 | 语言类型按照文件后缀名判断。每种语言类型下有不同的规则。
20 |
21 | 规则的格式为。
22 | ```
23 | "rule_name": {"keyword":[], "discription": "test", "pattern": "regexp", bbais, ebias}
24 | ```
25 |
26 | 其中
27 | * keyword 鼠标悬浮位置
28 | * discription 悬浮显示内容,html显示
29 | * pattern 正则表达式
30 | * abais 开始处偏移
31 | * bbais 结尾处偏移
32 | * enable 是否启用该条规则
33 |
34 | 本插件已经内置了php的一些规则,欢迎大家提交新的规则以及对现有规则进行优化。
35 |
36 | ## License
37 |
38 | MIT
39 |
40 | ## 联系
41 |
42 | http://5alt.me
43 |
44 | md5_salt [AT] qq.com
45 |
46 | ## 参考资料
47 |
48 | Seay代码审计系统
49 |
50 | https://www.sublimetext.com/docs/3/api_reference.html
51 |
52 | https://cnpagency.com/blog/creating-sublime-text-3-plugins-part-1/
53 |
54 | https://github.com/bradrobertson/sublime-packages
55 |
56 | http://zxhfighter.github.io/blog/javascript/2013/07/30/sublime-plugin.html
57 |
58 |
--------------------------------------------------------------------------------
/VulData.sublime-settings:
--------------------------------------------------------------------------------
1 | // author: md5_salt
2 | /*
3 | "rule_name": {"keyword":[], "discription": "test", "pattern": "regexp", bbais, ebias}
4 | keyword 鼠标悬浮位置
5 | discription 悬浮显示内容
6 | pattern 正则表达式
7 | abais 开始处偏移
8 | bbais 结尾处偏移
9 | enable 是否启用该条规则
10 | */
11 |
12 | {
13 | "php":{
14 | "extract": {
15 | "keyword": ["extract"],
16 | "discription": "extract默认覆盖已有变量",
17 | "pattern": "\\b(extract)\\s{0,5}\\(.{0,30}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}\\s{0,5},{0,1}\\s{0,5}(EXTR_OVERWRITE){0,1}\\s{0,5}\\)",
18 | "abais": 0,
19 | "bbais": 0,
20 | "enable": 1
21 | },
22 | "parse_str": {
23 | "keyword": ["parse_str"],
24 | "discription": "parse_str对字符串进行一次url解码,同名变量覆盖前面的",
25 | "pattern": "\\b(mb_){0,1}parse_str\\s{0,10}\\(.{0,40}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}",
26 | "abais": 0,
27 | "bbais": -1,
28 | "enable": 1
29 | },
30 | "parse_url": {
31 | "keyword": ["parse_url"],
32 | "discription": "
- 可能返回null
- 与浏览器不一致http://a.com\\@5alt.me
- 与curl不一致http://x:x@a.com:80@5alt.me
",
33 | "pattern": "\\b(parse_url)\\(",
34 | "abais": 0,
35 | "bbais": -1,
36 | "enable": 1
37 | },
38 | "weak_equal": {
39 | "keyword": ["=="],
40 | "discription": "弱类型比较",
41 | "pattern": "[^=!]==[^=]",
42 | "abais": 1,
43 | "bbais": -1,
44 | "enable": 1
45 | },
46 | "class_dynamic_call": {
47 | "keyword": ["->$"],
48 | "discription": "动态调用",
49 | "pattern": "->\\$",
50 | "abais": 0,
51 | "bbais": 0,
52 | "enable": 1
53 | },
54 | "preg_match": {
55 | "keyword": ["preg_match", "preg_match_all"],
56 | "discription": "是否有起始和结束标记,在m的情况下.不匹配换行%0a绕过,$匹配行尾或者换行符",
57 | "pattern": "\\b(preg_match|preg_match_all)\\(",
58 | "abais": 0,
59 | "bbais": -1,
60 | "enable": 1
61 | },
62 | "preg_replace": {
63 | "keyword": ["preg_replace"],
64 | "discription": "preg_replace中的replacement部分可以有特殊含义的标记 \\0或者$0",
65 | "pattern": "\\b(preg_replace)\\(",
66 | "abais": 0,
67 | "bbais": -1,
68 | "enable": 1
69 | },
70 | "in_array": {
71 | "keyword": ["in_array"],
72 | "discription": "是否非严格模式比较",
73 | "pattern": "\\b(in_array)\\(",
74 | "abais": 0,
75 | "bbais": -1,
76 | "enable": 1
77 | },
78 | "array_search": {
79 | "keyword": ["array_search"],
80 | "discription": "是否非严格模式比较",
81 | "pattern": "\\b(array_search)\\(",
82 | "abais": 0,
83 | "bbais": -1,
84 | "enable": 1
85 | },
86 | "unserialize": {
87 | "keyword": ["unserialize"],
88 | "discription": "是否完全可控,用UAF打",
89 | "pattern": "\\b(unserialize)\\(",
90 | "abais": 0,
91 | "bbais": -1,
92 | "enable": 1
93 | },
94 | "file_operation":{
95 | "keyword": ["unlink", "copy", "fwrite", "file_put_contents", "bzopen"],
96 | "discription": "跨目录,是否存在条件竞争,windows下通配符、NTFS流",
97 | "pattern": "\\b(unlink|copy|fwrite|file_put_contents|bzopen)\\s{0,10}\\(.{0,40}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}",
98 | "abais": 0,
99 | "bbais": 0,
100 | "enable": 1
101 | },
102 | "str_replace": {
103 | "keyword": ["str_replace"],
104 | "discription": "多次出现绕过",
105 | "pattern": "\\b(str_replace)\\(",
106 | "abais": 0,
107 | "bbais": -1,
108 | "enable": 1
109 | },
110 | "urldecode": {
111 | "keyword": ["urldecode"],
112 | "discription": "二次url编码",
113 | "pattern": "\\b(urldecode)\\(",
114 | "abais": 0,
115 | "bbais": -1,
116 | "enable": 1
117 | },
118 | "callback": {
119 | "keyword": ["uasort", "uksort", "usort", "array_diff_uassoc", "array_diff_ukey", "array_filter", "array_intersect_uassoc", "array_intersect_ukey", "array_map", "array_reduce", "array_udiff_assoc", "array_udiff_uassoc", "array_udiff_uassoc", "array_udiff", "array_uintersect_assoc", "array_uintersect_uassoc", "array_uintersect_uassoc", "array_uintersect", "array_walk_recursive", "array_walk", "call_user_func_array", "call_user_func", "preg_replace_callback"],
120 | "discription": "回调函数",
121 | "pattern": "\\b(uasort|uksort|usort|array_diff_uassoc|array_diff_ukey|array_filter|array_intersect_uassoc|array_intersect_ukey|array_map|array_reduce|array_udiff_assoc|array_udiff_uassoc|array_udiff_uassoc|array_udiff|array_uintersect_assoc|array_uintersect_uassoc|array_uintersect_uassoc|array_uintersect|array_walk_recursive|array_walk|call_user_func_array|call_user_func|preg_replace_callback)\\(",
122 | "abais": 0,
123 | "bbais": -1,
124 | "enable": 1
125 | },
126 | "function_dynamic_call": {
127 | "keyword": [],
128 | "discription": "$a()动态函数调用",
129 | "pattern": "\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}\\s{0,5}\\(",
130 | "abais": 0,
131 | "bbais": -1,
132 | "enable": 1
133 | },
134 | "double$": {
135 | "keyword": [],
136 | "discription": "$$可能变量覆盖",
137 | "pattern": "\\${{0,1}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}\\s{0,4}=\\s{0,4}.{0,20}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}",
138 | "abais": 0,
139 | "bbais": 0,
140 | "enable": 1
141 | },
142 | "system":{
143 | "keyword": ["system", "passthru", "pcntl_exec", "shell_exec", "escapeshellcmd", "exec"],
144 | "discription": "命令执行",
145 | "pattern": "\\b(system|passthru|pcntl_exec|shell_exec|escapeshellcmd|exec)\\s{0,10}\\(.{0,40}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}",
146 | "abais": 0,
147 | "bbais": 0,
148 | "enable": 1
149 | },
150 | "rand":{
151 | "keyword": ["rand", "mt_rand"],
152 | "discription": "随机数预测",
153 | "pattern": "\\b(rand|mt_rand|srand|mt_srand)\\(",
154 | "abais": 0,
155 | "bbais": 0,
156 | "enable": 1
157 | },
158 | "iconv":{
159 | "keyword": ["iconv"],
160 | "discription": "字符串编码转换,字符串截断问题,宽字节问题,Unicode替换问题",
161 | "pattern": "\\b(iconv)\\(",
162 | "abais": 0,
163 | "bbais": -1,
164 | "enable": 1
165 | },
166 | "json_decode":{
167 | "keyword": ["json_decode"],
168 | "discription": "json解码,可能存在弱类型比较问题",
169 | "pattern": "\\b(json_decode)\\(",
170 | "abais": 0,
171 | "bbais": 0,
172 | "enable": 1
173 | },
174 | "sprintf":{
175 | "keyword": ["sprintf", "vsprintf"],
176 | "discription": "php的格式化字符串问题,格式化字符串是否可控",
177 | "pattern": "\\b(sprintf|vsprintf)\\s{0,10}\\(",
178 | "abais": 0,
179 | "bbais": -1,
180 | "enable": 1
181 | },
182 | "env_xss":{
183 | "keyword": ["PATH_INFO", "PHP_SELF"],
184 | "discription": "这两个环境变量没有经过url编码,可能引发xss",
185 | "pattern": "\\b(PATH_INFO|PHP_SELF)\\(",
186 | "abais": 0,
187 | "bbais": 0,
188 | "enable": 1
189 | },
190 | "escapeshellarg":{
191 | "keyword": ["escapeshellarg"],
192 | "discription": "将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,不能用escapeshellcmd再次处理",
193 | "pattern": "\\b(escapeshellarg)\\(",
194 | "abais": 0,
195 | "bbais": 0,
196 | "enable": 1
197 | },
198 | "escapeshellcmd":{
199 | "keyword": ["escapeshellcmd"],
200 | "discription": "会对|*?~<>^()[]{}$\\, \\x0A 和 \\xFF进行转义,'和 \"仅在不配对儿的时候被转义,不能用escapeshellarg再次处理",
201 | "pattern": "\\b(escapeshellcmd)\\(",
202 | "abais": 0,
203 | "bbais": 0,
204 | "enable": 1
205 | },
206 | "basename":{
207 | "keyword": ["basename"],
208 | "discription": "在win平台下,`\\/`都可以作为basename的分隔符,但是在Linux平台下只有`/`可以作为分隔。windows下之前如果用了addslashes会导致文件名中单引号逃逸。",
209 | "pattern": "\\b(basename)\\(",
210 | "abais": 0,
211 | "bbais": 0,
212 | "enable": 1
213 | },
214 | "getimagesize":{
215 | "keyword": ["getimagesize"],
216 | "discription": "windows平台如果传入路径可控,可用于猜测文件夹路径(dedecms找后台)",
217 | "pattern": "\\b(getimagesize)\\(",
218 | "abais": 0,
219 | "bbais": 0,
220 | "enable": 1
221 | },
222 | "$_SERVER":{
223 | "keyword": ["$_SERVER"],
224 | "discription": "$_SERVER 大多没过滤,伪造host等,自行解析REQUEST_URI和$_GET结果不一致,PHP_SELF部分可控",
225 | "pattern": "\\b(\\$_SERVER\\[)\\(",
226 | "abais": 0,
227 | "bbais": 0,
228 | "enable": 1
229 | }
230 | },
231 | "js":{
232 | "window.addEventListener":{
233 | "keyword": ["addEventListener"],
234 | "discription": "监听message事件",
235 | "pattern": "\\b(addEventListener)\\s{0,10}\\(['\"]\\b(message)['\"]",
236 | "abais": 0,
237 | "bbais": 0,
238 | "enable": 1
239 | },
240 | "chrome.runtime.onMessage":{
241 | "keyword": ["onMessage"],
242 | "discription": "插件onMessage",
243 | "pattern": "\\b(chrome\\.runtime\\.onMessage)",
244 | "abais": 0,
245 | "bbais": 0,
246 | "enable": 1
247 | },
248 | "chrome.runtime.onMessageExternal":{
249 | "keyword": ["onMessageExternal"],
250 | "discription": "插件接收外部消息onMessageExternal",
251 | "pattern": "\\b(chrome\\.runtime\\.onMessageExternal)",
252 | "abais": 0,
253 | "bbais": 0,
254 | "enable": 1
255 | },
256 | "chrome.runtime.onConnectExternal":{
257 | "keyword": ["onConnectExternal"],
258 | "discription": "插件接收外部连接onConnectExternal",
259 | "pattern": "\\b(chrome\\.runtime\\.onConnectExternal)",
260 | "abais": 0,
261 | "bbais": 0,
262 | "enable": 1
263 | },
264 | "location":{
265 | "keyword": ["location"],
266 | "discription": "location操作",
267 | "pattern": "\\b(location)[\\.\\s=]",
268 | "abais": 0,
269 | "bbais": -1,
270 | "enable": 1
271 | },
272 | "innerHTML":{
273 | "keyword": ["innerHTML"],
274 | "discription": "innerHTML操作",
275 | "pattern": "\\.innerHTML",
276 | "abais": 0,
277 | "bbais": 0,
278 | "enable": 1
279 | },
280 | "html":{
281 | "keyword": ["html"],
282 | "discription": "jQuery .html() 操作",
283 | "pattern": "\\.html\\s{0,10}\\(\\s{0,10}[^)]",
284 | "abais": 1,
285 | "bbais": -2,
286 | "enable": 1
287 | },
288 | "document.write":{
289 | "keyword": ["write"],
290 | "discription": "document.write",
291 | "pattern": "\\b(document\\.write)\\s{0,10}\\(",
292 | "abais": 0,
293 | "bbais": -1,
294 | "enable": 1
295 | },
296 | "eval":{
297 | "keyword": ["eval"],
298 | "discription": "eval",
299 | "pattern": "\\b(eval)\\s{0,10}\\(",
300 | "abais": 0,
301 | "bbais": -1,
302 | "enable": 1
303 | },
304 | "setTimeout":{
305 | "keyword": ["setTimeout"],
306 | "discription": "setTimeout第一个参数是字符串",
307 | "pattern": "\\b(setTimeout)\\s{0,10}\\(\\s{0,10}['\"]",
308 | "abais": 0,
309 | "bbais": -1,
310 | "enable": 1
311 | },
312 | "setInterval":{
313 | "keyword": ["setInterval"],
314 | "discription": "setInterval第一个参数是字符串",
315 | "pattern": "\\b(setInterval)\\s{0,10}\\(\\s{0,10}['\"]",
316 | "abais": 0,
317 | "bbais": -1,
318 | "enable": 1
319 | },
320 | }
321 |
322 | }
--------------------------------------------------------------------------------
/VulHint.py:
--------------------------------------------------------------------------------
1 | # author: md5_salt
2 | import sublime
3 | import sublime_plugin
4 |
5 | import re
6 |
7 | g_regions = []
8 | g_region_lines = []
9 | g_jump_index = 0
10 |
11 | g_line_regions = {}
12 |
13 | class VulHint(sublime_plugin.EventListener):
14 | lang = None
15 | data = {}
16 |
17 | def on_load_async(self, view):
18 | if not sublime.load_settings("plugin.sublime-settings").get("enable", 1):
19 | return
20 | self.init(view)
21 | self.mark_vul(view)
22 | get_lines(view)
23 |
24 | def on_post_save_async(self, view):
25 | if not sublime.load_settings("plugin.sublime-settings").get("enable", 1):
26 | return
27 | global g_regions
28 | self.init(view)
29 | self.mark_vul(view)
30 | get_lines(view)
31 |
32 | def on_hover(self, view, point, hover_zone):
33 | if not sublime.load_settings("plugin.sublime-settings").get("enable", 1):
34 | return
35 | global g_regions
36 | global g_region_lines
37 | global g_jump_index
38 |
39 | global g_line_regions
40 |
41 | if not self.lang or not self.data:
42 | return
43 | #self.init(view)
44 | # locate smiles in the string. smiles string should be at the beginning and followed by tab (cxsmiles)
45 | # hovered_line_text = view.substr(view.word(point)).strip()
46 | #hovered_line_text = view.substr(view.line(point)).strip()
47 | if (hover_zone == sublime.HOVER_TEXT):
48 | word = view.substr(view.word(point)).strip()
49 | for key in g_regions:
50 | val = self.data[key]
51 | if word in val["keyword"]:
52 | hovered_text = '%s
'%(val["discription"])
53 | view.show_popup(hovered_text,
54 | flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY,
55 | location=point)
56 | g_jump_index = g_region_lines.index(view.rowcol(point)[0])
57 | return
58 | line = view.rowcol(point)[0]
59 | if g_line_regions.get(line):
60 | hovered_text = ''
61 | for key in g_line_regions.get(line):
62 | val = self.data[key]
63 | hovered_text += '%s
'%(val["discription"])
64 | view.show_popup(hovered_text, flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY, location=point)
65 | g_jump_index = g_region_lines.index(view.rowcol(point)[0])
66 |
67 | return
68 |
69 | def init(self, view):
70 | global g_regions
71 | clear_mark(view)
72 | g_regions = []
73 | self.lang = self.guess_lang(view)
74 | if self.lang in ['html', 'htm']:
75 | self.lang = 'js'
76 | self.data = sublime.load_settings("VulData.sublime-settings").get(self.lang, {})
77 |
78 | def mark_vul(self, view):
79 | global g_regions
80 | #print([self.data[i]["discription"] for i in self.data])
81 | if not self.lang or not self.data:
82 | return
83 | for key,val in self.data.items():
84 | if not val['enable']: continue
85 | vul = view.find_all(val['pattern'])
86 | if not vul: continue
87 | for i in vul:
88 | i.a += val["abais"]
89 | i.b += val["bbais"]
90 | view.add_regions(key, vul, "string", "cross", sublime.DRAW_OUTLINED|sublime.DRAW_STIPPLED_UNDERLINE)
91 | g_regions.append(key)
92 |
93 |
94 | def guess_lang(self, view=None, path=None, sublime_scope=None):
95 | if not view:
96 | return None
97 | filename = view.file_name()
98 | return filename.split('.')[-1].lower()
99 |
100 |
101 | def clear_mark(view):
102 | global g_regions
103 | if not g_regions: return
104 | for i in g_regions:
105 | view.erase_regions(i)
106 |
107 | def get_lines(view):
108 | global g_regions
109 | global g_region_lines
110 | global g_line_regions
111 |
112 | g_line_regions = {}
113 | g_region_lines = set()
114 | for region in g_regions:
115 | for i in view.get_regions(region):
116 | line = view.rowcol(i.a)[0]
117 | g_region_lines.add(line)
118 | if g_line_regions.get(line, None):
119 | g_line_regions[view.rowcol(i.a)[0]].add(region)
120 | else:
121 | g_line_regions[view.rowcol(i.a)[0]] = set([region])
122 | g_region_lines = sorted(g_region_lines)
123 |
124 |
125 |
126 | class GotoNextCommand(sublime_plugin.TextCommand):
127 |
128 | def run(self, edit):
129 | global g_jump_index, g_region_lines
130 | # Convert from 1 based to a 0 based line number
131 | line = g_region_lines[g_jump_index]
132 | g_jump_index = (g_jump_index + 1)%len(g_region_lines)
133 |
134 | # Negative line numbers count from the end of the buffer
135 | if line < 0:
136 | lines, _ = self.view.rowcol(self.view.size())
137 | line = lines + line + 1
138 |
139 | pt = self.view.text_point(line, 0)
140 |
141 | self.view.sel().clear()
142 | self.view.sel().add(sublime.Region(pt))
143 |
144 | self.view.show(pt)
145 |
146 | class EnableCommand(sublime_plugin.TextCommand):
147 | def run(self, edit):
148 | sublime.load_settings("plugin.sublime-settings").set("enable", 1)
149 | sublime.save_settings("plugin.sublime-settings")
150 |
151 | class DisableCommand(sublime_plugin.TextCommand):
152 | def run(self, edit):
153 | sublime.load_settings("plugin.sublime-settings").set("enable", 0)
154 | sublime.save_settings("plugin.sublime-settings")
155 |
156 | class ClearCommand(sublime_plugin.TextCommand):
157 | def run(self, edit):
158 | clear_mark(self.view)
--------------------------------------------------------------------------------
/plugin.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | "enable": 1
3 | }
--------------------------------------------------------------------------------