- 里论外几
-
SublimeText2的扩展模型是相当的功能全面。你可以改变语法高亮,实际的编辑器外观,以及所有的菜单项。此外,还可以创建新的build环境,自动补全,语言定义,代码区段,宏,键绑定,鼠标绑定以及插件。所有这些不同形式的改装都是用组织在package中的文件来实现的。 所谓pacakage就是一个存储在你的Packages目录中的文件夹。你可以点击Preferences>BrowsePackages…菜单进入你的Packages目录。也可以通过创建一个zip文件并且把扩展名改为.sublime-package来实现把pacakage打包成一个单独文件。我们将在本教程中讨论一点怎么打包。 Sublime绑定了很多不同的package。大不多数绑定的都是和特定语言相关的package,包括语言定义,自动补全以及build环境。除了语言相关的package,还有两个Default和Userpackage。Defaultpackage包含了所有的标准键绑定,菜单定义,文件设置和一大堆用python写的插件。Duringtheprocessofwritingaplugin,theSublimeText2APIreferencewillbeessential. 要写一个插件,SublimeText2APIreference是根本。此外,Defaultpackage对于怎么做我们的工作也是一个很好的参考。编辑器的大部分功能都是通过commans命令来实现,除了敲入字符之外的所有操作都可以通过commans完成。查看Preferences>KeyBindings–Defaultmenu,你可以找到很多有用的内建的功能。 现在,pacakge和产检的区别已经清楚了,可以开始写我们的插件了。 第一步-起步 Sublime有一个功能可以产生一个简单插件所需要的Python代码框架。选择Tools>NewPlugin…菜单,可以打开一个新的文件,带有下面的样式:12345importsublime,sublime_pluginclassExampleCommand(sublime_plugin.TextCommand):defrun(self,edit):self.view.insert(edit,0,"Hello,World!") 可以看到,引入了两个SublimePython的模块,使得我们可以访问其API并且创建一个新的类。在开始编辑创建我们自己的插件之前,请先保存这个文件。 要保存这个文件我们需要创建一个package来保存它。按下ctrl+s(Windows/Linux)orcmd+s(OSX)来保存文件。保存对话框默认打开Userpackage,不要把我们的文件存在那里,而是创建一个新的文件夹,命名为Prefixr。12345678910Packages/…-OCaml/-Perl/-PHP/-Prefixr/-Python/-R/-Rails/… 现在,把我们的文件保存在Prefixr文件夹中,命名为Prefixr.py。其实文件名并不重要,只要以.py为扩展名就可以。但方便起见,还是用我们的插件的名字吧。 现在,插件已经做了保存。我们可以试着运行了。输入ctrl+`打开Sublime的控制台,这是一个可以访问API的Python控制台。输入下面的Python代码来测试我们的新插件:1view.run_command("example") 你将看到HelloWorld被插入到了我们的插件文件的开头。接下来继续之前先Undo掉这个新的插入。 第二步-Comman的类型和名字 对于一个插件,Sublime提供了三种类型的command。Textcommands提供通过一个View对象访问被选定的文件或者buffer的内的能力Windowcommands提供一个Window对象,可引用当前的窗口Applicationcommands没有引用任何特定的窗口,文件或者buffer,很少使用。 因为我们要用我们的插件来操作CSS文件或者buffer里面的内容,所以我们要使用sublime_plugin.TextCommand类作为我们定制的Prefixr命令的基类。这时,我们就需要命名我们命令的类名了。 在我们的代码框架中,你可以看到下面的类:1classExampleCommand(sublime_plugin.TextCommand): 在我们运行命令时,在控制台中执行的是下面的代码:1view.run_command("example") Sublime将把继承自任意一个sublime_plugin类(TextCommand,WindowCommandorApplicationCommand)的类的名字的Command后缀去掉,并且用下划线符号命名替换驼峰式命名。 这样一来,为创建一个名字是prefixr的command,类名就必须是PrefixrCommand。1classPrefixrCommand(sublime_plugin.TextCommand): 第三步-选定文本 Sublime最有用的功能之一就是具备多行选定的功能 现在,我们已经正式命名了我们的插件,可以开始从当前的buffer中获取CSS并且发送到PrefixrAPI上了。Sublime最有用的功能之一就是具备多行选定的功能。由于要获取选定的文件,我们需要把所有选定的行放入我们的插件中处理,而不仅仅是第一个选定的。 由于我们写的是一个文本命令,所以可以通过self.view访问当前view。view对象的self()方法将返回一个当前选定内容的iterableRegion集合,我们可以通过花括号扫描到这些内容。若找不到花括号,可以扩大选定内容到周围的括号,以保证整个块有一个括号前缀。选定内容中是否包含花括号还将有利于我们后面对PrefixrAPI返回的内容作空白调整和格式调整。12345braces=Falsesels=self.view.sel()forselinsels:ifself.view.substr(sel).find("{")!=-1:braces=True 用这几行代码替换框架中的run()方法中的代码。 若未找到任何的花括号,我们需要循环检测每一个选定区段,把每一个区段和后括弧关联起来。之后,用带有to参数设置为brackets的内建命令expand_selectionl来确保获取了每个CSS块的完整内容。12345678ifnotbraces:new_sels=[]forselinsels:new_sels.append(self.view.find("}",sel.end()))sels.clear()forselinnew_sels:sels.add(sel)self.view.run_command("expand_selection",{"to":"brackets"}) 若果你想再检查一次你的代码,你可以和源代码zip文件中的Prefixr1.py文件对比一下。 第四步-线程为防止糟糕的连接破坏其他正常工作,我们需要确保在后台完成PrefixrAPI调用。 此时,选定的文本已经扩展到了能抓取每个CSS块的完整内容。现在,我们需要把他们发送打牌PrefixrAPI上。这只需要一个简单的HTTP请求,用urllib和urllib2模块就可以实现。但是,在发起请求之前,需要想想一个潜在的web请求延迟会对编辑器性能造成的影响。如因为某些原因,用户处在一个很慢的连接环境下,对PrefixrAPI的请求很可能需要好几秒钟乃至。 为防止糟糕的连接破坏其他正常工作,我们需要确保在后台完成PrefixrAPI调用。若你不了解线程,很基础的一种解释就是,线程是使你的程序的多个代码块在同一时间运行的机制。这在我们的插件环境中是很重要的,因为这样做可以避免在向Prefixr发送数据和等待响应的过程中Sublime处于不可用状态。 第五步-创建线程 我们将使用Pythonthreading模块来创建线程。要使用该模块,需要创建一个继承threading的新类。Thread包含一个所有线程代码都在其中执行的run()方法。123456789101112131415161718192021222324classPrefixrApiCall(threading.Thread):def__init__(self,sel,string,timeout):self.sel=selself.original=stringself.timeout=timeoutself.result=Nonethreading.Thread.__init__(self)defrun(self):try:data=urllib.urlencode({"css":self.original})request=urllib2.Request("/api/index.php",data,headers={"User-Agent":"SublimePrefixr"})http_file=urllib2.urlopen(request,timeout=self.timeout)self.result=http_file.read()returnexcept(urllib2.HTTPError)as(e):err="%s:HTTPerror%scontactingAPI"%(__name__,str(e.code))except(urllib2.URLError)as(e):err="%s:URLerror%scontactingAPI"%(__name__,str(e.reason))sublime.error_message(err)self.result=False 这里,我们使用thread的__init__()方法来设置所有的在web请求中需要的数据变量。run()方法包含所有的设置代码和执行向PrefixrAPI的http请求的代码。由于线程并发的执行,所以直接返回值是不可行的,取而代之的我们设置self.result作为调用的结果。 鉴于我们在我们的插件中开始使用其他一些模块,我们必须在程序顶端增加import语句。123importurllibimporturllib2importthreading 现在我们有了一个线程类来执行HTTP请求,我们需要为每一个selection块创建一个线程。为此,回到我们的PrefixrCommand类的run方法中,使用下面的循环:123456threads=[]forselinsels:string=self.view.substr(sel)thread=PrefixrApiCall(sel,string,5)threads.append(thread)thread.start() 我们记录了每一个我们创建的线程,然后调用start()方法开启每一个线程。如果想再次检查你的工作,对比源代码文件zip文件中的filePrefixr1.py文件。 第六步-准备结果 现在,我们已经开始了实际的PrefixrAPI请求,在处理HTTP相应之前,我们需要处理最后几个问题。 首先,我们清楚所有的selection,因为之前我们修改了他们。稍后将把他们设置成一个更合理的状态。1self.view.sel().clear() 此外,我们开启一个新的Edit对象,把undo和redo操作组织在一起。我们指定我们在为prefix命令创建一个这样的组。1edit=self.view.begin_edit("prefixr") 作为最后一步,我们调用下面的方法来处理API的相应结果。 第七步-处理线程 此时,我们的线程已经在运行了,认知已经运行结束了。下一次,我们需要完成刚刚调用的handle_threads()方法。这一方法将循环处理线程列表并且寻找不再运行的线程。12345678910defhandle_threads(self,edit,threads,braces,offset=0,i=0,dir=1):next_threads=[]forthreadinthreads:ifthread.is_alive():next_threads.append(thread)continueifthread.result==False:continueoffset=self.replace(edit,thread,braces,offset)threads=next_threads 如果线程仍然运行着,我们将其加入线程列表一遍后面再检测。如果result是False,则忽略它。然后,对于返回正确的结果,再调用一个马上就要说到的replace()方法。