自己动手开发sublime插件(1)
最近对sublime的亲密度不断增加,以前只是用它来写WEB,现在发现用它来写C++也很方便,特别是它的免打扰模式太好用了,于是我干脆弄了一个C++的sublime build system,这样在sublime中也能编译C++了,除了不能调试,已经算是一个开发环境。
但是,昨天发现,我习惯用的Visual Assist在sublime中找不到相似的插件。尤其是 alt + up, alt+ down 快捷键,在VC中是跳转到类或函数定义,是我最常用的快捷键之一。好吧,既然找不到,咱就自己做一个。
其实为sublime做插件并不难,sublime是用python做为脚本驱动的,插件通常也是用python。在网上看了点资料,再看看sublime3\package\default目录里面几个py,就大概明白插件的思路了。
首先,在 sublime3安装目录 \Data\Packages目录下新建一个目录,名字可以随意,我取名为JumpTag。
然后,因为我要捕捉快捷键,就要有.sublime-keymap文件;要用python,必须有.py文件。所以在这个JumpTag目录下新建几个文件:Default (Linux).sublime-keymap、Default (OSX).sublime-keymap、Default (Windows).sublime-keymap 和 JumpTag.py
其中,三个.sublime-keymap文件都是一样:
[ { "keys": ["alt+up"], "command": "jump_tag_prev"}, { "keys": ["alt+down"], "command": "jump_tag_next"} ]
其实是json格式,意思是说按下 alt+up 就调用命令 jump_tag_prev,alt+down 调用 jump_tag_next
那么,在哪里实现jump_tag_prev、jump_tag_next 命令呢?当然是在.py文件里啦:
1 import sublime, sublime_plugin 2 import re 3 4 5 _datas = {"view_id":0,"recorder":None} 6 7 class JumpTagRecorder(sublime_plugin.EventListener): 8 def on_modified_async(self, view): 9 _datas["view_id"] = view.id(); 10 _datas["recorder"]= None; 11 12 def on_activated_async(self, view): 13 _datas["view_id"] = 0; 14 _datas["recorder"]= None; 15 16 17 def gen_recorder(view): 18 if (_datas["recorder"] is None or _datas["view_id"] != view.id()): 19 _datas["view_id"] = view.id() 20 _datas["recorder"]= view.find_all(r"(^(?![ \t]*?\b(if|while|for|try)\b)[\w \t*&]*?\([^()]*?\)(?=[\w\s]*?\{))|(\bclass\b|\bstruct\b)[\w \t:,<>]+?(?=[\s]*?\{)") 21 22 return _datas["recorder"] 23 24 25 class JumpTagPrevCommand(sublime_plugin.TextCommand): #注意看类的名字,对应了 jump_tag_prev_command 命令 26 def run(self, edit): 27 reg_list = gen_recorder(self.view) 28 29 if reg_list is None: 30 return 31 32 for region in self.view.sel(): 33 pt = region 34 break 35 36 closest_num = 999999999 37 closest_region = sublime.Region(0,0) 38 39 for region in reg_list: 40 if region.end() < pt.begin(): 41 n = pt.begin() - region.end() 42 if n < closest_num: 43 closest_num = n 44 closest_region = region 45 46 if closest_region.empty(): 47 return 48 49 self.view.sel().clear() 50 self.view.sel().add(closest_region) 51 52 self.view.show(closest_region.begin()) 53 54 class JumpTagNextCommand(sublime_plugin.TextCommand): #这个类的名字对应了 jump_tag_next_command 命令 55 def run(self, edit): 56 57 reg_list = gen_recorder(self.view) 58 59 if reg_list is None: 60 return 61 62 for region in self.view.sel(): 63 pt = region 64 break 65 66 closest_num = 999999999 67 closest_region = sublime.Region(0,0) 68 69 for region in reg_list: 70 if region.begin() > pt.end(): 71 n = region.begin() - pt.end() 72 if n < closest_num: 73 closest_num = n 74 closest_region = region 75 76 if closest_region.empty(): 77 return 78 79 self.view.sel().clear() 80 self.view.sel().add(closest_region) 81 82 self.view.show(closest_region.begin())
稍微讲一下这代码:因为我们要在函数定义、类定义之间跳转,所以就得先把这些定义的位置找出来,函数gen_recorder就是干这工作的,我承认函数名起的不咋滴。它调用了sublime的一个API:find_all,按正则表达式找出所有函数定义和类定义的位置,我这正则表达式也不知道是不是最优,总之功能是实现了。
现在有了找出定义位置的函数了,那么,我们什么时候才需要去找这些定义的位置呢?应该知道全文查找是个相对耗时的操作,不应该在每次按快捷键时调用。我们可以把定义的位置保存起来,在文档发生改变时再重新查找。因此,就需要监听文档的修改事件了。定义一个监听器也是很简单的:
class JumpTagRecorder(sublime_plugin.EventListener): #注意括号里必须是sublime_plugin.EventListener def on_modified_async(self, view): #注意函数的名字 _datas["view_id"] = view.id(); _datas["recorder"]= None; def on_activated_async(self, view): _datas["view_id"] = 0; _datas["recorder"]= None;
上面这个类的名字是随意的,但是要注意括号里面,必须是 sublime_plugin.EventListener,类的方法必须按照api文档http://www.sublimetext.com/docs/3/api_reference.html
最后,就是实现 jump_tag_prev、jump_tag_next命令了,代码十分的简单,相信没有人看不懂的。要注意的是命令对应的类,不能随意取名,一定要把命令名改成“驼峰式”,还得加上Command,而且必须要有run方法。
简简单单,就实现一个小插件啦!对了,让我影响比较深的是sublime是热加载插件代码的,也就是说插件代码一旦被修改,sublime中立即重新加载,这真的很方便。