前言:术语和参考资料

sublime text 2的扩展模式相当的丰富。有多种方法可以修改语法高亮模式以及所有的菜单等。它还可以创建一个新的build系统,自动补全,语言定义,代码片段,宏定义,快捷键绑定,鼠标事件绑定和插件。所有这些都是通过文件构成的包来实现。
一 个包就是在'Packages'目录下的一个文件夹,可以通过Preferences > Browse Packages…菜单访打开改目录。也可以把一个包大包成一个zip文件,然后把扩展名改成.sublime-package。后面会有更多关于打包的 介绍。
Sublime默认就捆绑了很多包。大部分的包都是跟特定语言相关的。包里面包含了语言定义,自动补全和build系统。另 外还有2个包:Default和User。Default包里包含了所有标准的键盘绑定,菜单定义,文件配置和一大堆用python写的插件。User包 比较特殊,它总是在最后加载。通过在User包里的自定义文件,它允许用户重写所有默认行为。
要写好插件,好手册当然是必须的:Sublime Text 2 API reference
Default包里的东西也是个很好的参考,可以掘墓下前人是如何做的,哪些是可能实现的。
大部分的编辑器都提供命令功能,除了输入字符之外的所有操作都可以通过命令来完成。Preferences > Key Bindings – Default 通过这个菜单可以看到所有内置的命令。
另外,sublime插件需要使用python开发,它内置了python环境,那个控制台其实也是个python控制台。
泪奔啊,貌似除了前端技术外我能懂的就是python了。。。
OK,了解了下插件和包机制,可以开始写个插件玩玩。
 

Step1-创建一个插件

 sublime要写一个简单的插件,首先要创建一个python骨架的代码。
通过Tools > New Plugin…菜单就可以自动创建一个插件的样板。
 
import sublime, sublime_plugin 
class ExampleCommand(sublime_plugin.TextCommand): 
    def run(self, edit): 
        self.view.insert(edit, 0, "Hello, World!")
 
import了2个模块,创建了一个command的类。我们先保存下并运行下试试。
保存的时候要创建一个包。保存弹出框默认是在Packages\User目录下,No,我们要创建一个自己的包保存。在Packages目录下创建一个Prefixr目录:
 
Packages/ 
… 
- OCaml/ 
- Perl/ 
- PHP/ 
- Prefixr/ 
- Python/ 
- R/ 
- Rails/ 
然 后把文件包存在Prefixr目录下命名为Prefixr.py。(因为原文的这篇教程是基于创建Prefixr这个插件的,其实我们安装的 sublime里已经有了这个插件包,所以自己试验的话可以随便取个别的名字,把它当成另外一个插件就好了。)Prefixr.py这个文件也可以是其它 名字,但必须要.py文件后缀,最好跟插件目录的名称一致。
这样,插件就保存好了。打开sublime的控制台ctrl+`。这其实就是一个Python控制台,可以在里面运行python代码。在控制台输入:
 
view.run_command('example')
就可以看到"Hello World"被插入在当前编辑器里激活的文件的开头。
记得撤销下,然后继续。。。 

Step2-Command类型和名称

sublime给插件提供了3中类型的command.
  • Text Commands提供了对当前View对象(就是正在编辑的文件)内容的访问。
  • Window Commands提供里对当前编辑器Window对象的引用。
  • Application Commands不提供对任何window或者文件的引用,而且也很少用到。
 
这么看来,我们要对CSS文件进行编辑就得用到sublime_plugin.TextCommand这个类。所以我们这个Prefixr command就继承了sublime_plugin.TextCommand。
 
class ExampleCommand(sublime_plugin.TextCommand):
然后向运行这个插件的时候就在控制台执行
 
view.run_command('example')
sublime会把所有继承自sublime_plugin(TextCommand,WindowCommand,ApplicationCommand)的类都去掉Command后缀,然后把驼峰格式转换成下划线格式,当做command的名称
所以,要创建一个prefixr的command,class名称就是PrefixrCommand.
 
class PrefixrCommand(sublime_plugin.TextCommand):
(依 次类推,类名为MyTestCommand的话,command的名称就是my_test,而用view.run_command('example') 运行这个插件的时候,'example'就是command名称,所以类名为MyTestCommand的话,则用 view.run_command('my_test')运行)。
 

Step3-选择文本

很好,现在我们的插件终于有个名字了,虽然看起来还是有点屌丝的味道。我们开始从当前的buffer获取css然后传给Prefix API来做些事情了。Sublime一个很强悍的功能就是可以方面的进行多选择。我们现在写的这个插件呢,当然就需要处理所有选中的文本。
text command类下可以通过self.view来访问当前的view,view的sel()方法返回当前所有选择区段的一个iterable。首先,我们 扫描下有没有花括号,如果没有就扩大到外围选区,来确定整个区域的前缀。不管有没有花括号,都可以帮助我们确定是否需要对Prefixr API返回的结果进行空格,格式化处理。
 
braces = False
sels = self.view.sel() 
for sel in sels: 
    if self.view.substr(sel).find('{') != -1: 
        braces = True
这段代码替代了run()方法的内容,直接执行。
如果我们没有找到花括号,就在查找每个选择最近的闭合的花括号,然后用内置的expand_selection命令,to参数设置为brackets 每个css规则区域内容就可以选中了。
 
if not braces: 
    new_sels = [] 
    for sel in sels: 
        new_sels.append(self.view.find('\}', sel.end())) 
    sels.clear() 
    for sel in new_sels: 
        sels.add(sel) 
    self.view.run_command("expand_selection", {"to": "brackets"})
可以参考代码库里的Prefixr-1.py
 

Step4-线程

现 在,选取已经扩展到了每个css代码块。就要把它发送给Prefixr API了。不用仰望,这只是一个小小的HTTP请求而已,用用urlib,urllib2这等模块就好了。但是我们先想想看,一个缓慢的web请求会对编 辑器的性能造成什么影响。如果Prefixr API的响应太慢,各位大师应该会很焦躁的。。。
所以应该把把这个请求处理放在后台悄悄地进行。这就要用到线程了。
其实呢,线程这玩意是Python本身的能力,跟这个啥sublime是没太大关系的,是吧。
 

Step5-创建线程

这里就要用到threading模块,创建一个PrefixrApiCall继承自threading.gThread,需要实现run方法,里面包含了需要运行的代码。
class PrefixrApiCall(threading.Thread): 
    def __init__(self, sel, string, timeout): 
        self.sel = sel 
        self.original = string 
        self.timeout = timeout 
        self.result = None
        threading.Thread.__init__(self) 
    def run(self): 
        try: 
            data = urllib.urlencode({'css': self.original}) 
            request = urllib2.Request('http://prefixr.com/api/index.php', data, 
                headers={"User-Agent": "Sublime Prefixr"}) 
            http_file = urllib2.urlopen(request, timeout=self.timeout) 
            self.result = http_file.read() 
            return
        except (urllib2.HTTPError) as (e): 
            err = '%s: HTTP error %s contacting API' % (__name__, str(e.code)) 
        except (urllib2.URLError) as (e): 
            err = '%s: URL error %s contacting API' % (__name__, str(e.reason)) 
        sublime.error_message(err) 
        self.result = False
__init__()方法里设置了做web请求时需要的一些值。run()方法里包含了创建http,请求Prefixr API的代码。因为线程是跟其它代码同时运行的,所以不能直接返回值。所以用self.result来保存调用的结果。
因为我们这里引入了很多其它模块了,所以要在头部加入import申明:
 
import urllib 
import urllib2 
import threading
【吐槽一下,这是python本身的东西,python是写插件的基础,这里就不用过多讲了。。】
现在我们有了线程类来做http请求了,我们要给每段选区的css创建一个线程。回到PrefixrCommand类的run()方法,用下面的代码:
 
threads = [] 
for sel in sels: 
    string = self.view.substr(sel) 
    thread = PrefixrApiCall(sel, string, 5) 
    threads.append(thread) 
    thread.start()
记录下每个创建的线程,然后调用线程的start()方法来启动它。
可以参考代码库里的Prefixr-2.py
 

Step6-为结果做准备

在处理Prefixr API请求的响应结果前我们还需要做点处理。
首先,清除掉所有的选区,因为我们之前做了修改。
 
self.view.sel().clear()
另外创建一个Edit对象。指定一组prefixr操作,组操作就可以方便重做和撤销。
 
edit = self.view.begin_edit('prefixr')
最后,调用一个我们后面会写的方法来处理API请求的响应。
 
self.handle_threads(edit, threads, braces)
 

Step7-处理线程

现在我们的线程们应该已经在高调的运行了,或者有些才飞了一会就结束了。现在就要实现前面用到的handle_threads()方法。这个方法遍历线程list检测显示是否还在运行。
 
def handle_threads(self, edit, threads, braces, offset=0, i=0, dir=1): 
    next_threads = [] 
    for thread in threads: 
        if thread.is_alive(): 
            next_threads.append(thread) 
            continue
        if thread.result == False: 
            continue
        offset = self.replace(edit, thread, braces, offset) 
    threads = next_threads
如果线程还在运行,把它添加到一个线程列表中,留校继续查看。如果查看失败就忽略,不过为了有更好的效果,后面会写一个replace()方法。
另外,作为一个前端工程师,当然要懂点用户体验。我们可以在状态栏告诉用户我们的插件是在努力工作的,没有偷懒哦。
 
if len(threads): 
    # This animates a little activity indicator in the status area 
    before = i % 8
    after = (7) - before 
    if not after: 
        dir = -1
    if not before: 
        dir = 1
    i += dir
    self.view.set_status('prefixr', 'Prefixr [%s=%s]' % \ 
        (' ' * before, ' ' * after)) 
    sublime.set_timeout(lambda: self.handle_threads(edit, threads, 
        braces, offset, i, dir), 100) 
    return
(还是需要不少python的知识。。。)
 
当所有线程都完成之后,就可以结束撤销的组标记了,然后通知下用户。
 
self.view.end_edit(edit) 
self.view.erase_status('prefixr') 
selections = len(self.view.sel()) 
sublime.status_message('Prefixr successfully run on %s selection%s' %
    (selections, '' if selections == 1 else 's'))
可以参考Prefixr-3.py文件代码
 

Step8-执行替换

正如前面提到的replace()方法,我们需要用Prefixr API返回的结果替换掉原来的css代码。
这个方法接受几个参数,撤销用的Edit对象,Prefixr API返回的结果,选区的偏移量
 
def replace(self, edit, thread, braces, offset): 
    sel = thread.sel 
    original = thread.original 
    result = thread.result 
    # Here we adjust each selection for any text we have already inserted 
    if offset: 
        sel = sublime.Region(sel.begin() + offset, 
            sel.end() + offset)
替换前对结果进行格式化一下,处理下空格,结束符等。
 
result = self.normalize_line_endings(result) 
(prefix, main, suffix) = self.fix_whitespace(original, result, sel, 
    braces) 
self.view.replace(edit, sel, prefix + main + suffix)
 
然后把选区扩展到新插入的CSS代码最后一个行的末尾,并返回便宜位置。
 
end_point = sel.begin() + len(prefix) + len(main) 
self.view.sel().add(sublime.Region(end_point, end_point)) 
return offset + len(prefix + main + suffix) - len(original)
可以参考代码库里的Prefixr-4.py文件
 

Step9-处理空白

前面替换的时候用到了一个normalize_line_endings()方法,将换行符转换成当前文档的换行符。
 
def normalize_line_endings(self, string): 
    string = string.replace('\r\n', '\n').replace('\r', '\n') 
    line_endings = self.view.settings().get('default_line_ending') 
    if line_endings == 'windows': 
        string = string.replace('\n', '\r\n') 
    elif line_endings == 'mac': 
        string = string.replace('\n', '\r') 
    return string
fix_whitespace()方法处理css块的缩进,空格,只能对单个css块做处理。
 
def fix_whitespace(self, original, prefixed, sel, braces): 
    # If braces are present we can do all of the whitespace magic 
    if braces: 
        return ('', prefixed, '')
 
另外,判断下原始css中的缩进。
 
(row, col) = self.view.rowcol(sel.begin()) 
indent_region = self.view.find('^\s+', self.view.text_point(row, 0)) 
if self.view.rowcol(indent_region.begin())[0] == row: 
    indent = self.view.substr(indent_region) 
else: 
    indent = ''
 
用当前文件的缩进设置来格式化prefixed的css
 
prefixed = prefixed.strip() 
prefixed = re.sub(re.compile('^\s+', re.M), '', prefixed) 
settings = self.view.settings() 
use_spaces = settings.get('translate_tabs_to_spaces') 
tab_size = int(settings.get('tab_size', 8)) 
indent_characters = '\t'
if use_spaces: 
    indent_characters = ' ' * tab_size 
prefixed = prefixed.replace('\n', '\n' + indent + indent_characters)
 
用开头的空白来判断下新插入的CSS代码位置是否正确。
match = re.search('^(\s*)', original) 
prefix = match.groups()[0] 
match = re.search('(\s*)\Z', original) 
suffix = match.groups()[0] 
return (prefix, prefixed, suffix)
 fix_whitespace()方法中用到了正则,所以要import re模块。
 
prefixr command就完成了,后面就是要做些快捷键绑定和菜单绑定了。
 

Step-10 键盘绑定

sublime 大部分的配置都可以通过json文件来完成,键盘绑定也一样。不过它的键盘绑定是区分系统的,所以基本上要建立3个文件,而且命名为Default (Windows).sublime-keymap, Default (Linux).sublime-keymap and Default (OSX).sublime-keymap
Prefixr/ 
... 
- Default (Linux).sublime-keymap 
- Default (OSX).sublime-keymap 
- Default (Windows).sublime-keymap 
- Prefixr.py
 
这个json文件里包含的是一个对象数组,每个对象需要包含keys,command,如果这个command需要参数的话还会有args。不过windows和linux的配置基本上差不多。
Preferences > Key Bindings – Default 可以通过这个菜单先查看下你想指定的快捷键是否已经被使用了。
 
    { 
        "keys": ["ctrl+alt+x"], "command": "prefixr"
    } 
]
 

Step-11 修改菜单

sublime有个很爽的事就是通过创建.sublime-menu文件就可以修改菜单。配置文件需要更具要修改的菜单类型来命名:
Main.sublime-menu 控制了程序的主菜单
Side Bar.sublime-menu 控制侧边栏文件或者目录的右键菜单
Context.sublime-menu 控制处于编辑状态的文件右键菜单
通过这种接口,通过一个菜单配置文件就可能会影响到其它的各个菜单。可以看看Default包下的已有的菜单配置。
我们想给我们的Prefixr插件在Edit菜单下添加一个菜单项,然后在Preferences里添加配置菜单。下面是Edit里的菜单配置,Preferences里的配置有点长就省略了。
    "id": "edit", 
    "children": 
    [ 
        {"id": "wrap"}, 
        { "command": "prefixr" } 
    ] 
]
 
注意这里的id的就一个已经存在的菜单结构。
可以参考代码库里的文件 https://github.com/wbond/sublime_prefixr
 

step-12 发布你的插件包

现在写了一个非常有用的插件了,当然要分享给别人用用。
“Sublime支持zip文件或者一个包目录来分享插件包。把包目录打包成一个zip文件,然后把后缀改成.sublime-package,别人把这个文件放到插件包目录下重启sublime就安装完成了。“
另外一种方式就是通过Package Control的插件,专门来管理插件安装的,相信你已经安装了。可以通过下面的步骤进行:
1).你需要有个github帐号,并fork https://github.com/wbond/package_control_channel
2).通过git clone命令下载你fork完的地址,如: git@github.com:welefen/package_control_channel.git
3).修改repositories.json这个文件,把你的插件名称和对应的github项目地址添加进去
4).ci并push到你的package ctrol里,然后通过pull 5).request推到官方的github里,如果他们审批通过了,那么你的插件就会放到package control里,别人就可以通过install直接安装了
(上面这段引用网络已有文章:http://www.welefen.com/how-to-develop-sublime-text-plugin.html,简短的插件开发入门也可以参考此文章。)
 
 

sublime text 插件开发的更多相关文章

  1. 【自己动手】sublime text插件开发

    今天是五四青年节,在此先祝大家节日快乐!!! --------------------------------------------华丽的分界线--------------------------- ...

  2. sublime text 插件 -- 获取文件名到剪贴板

    日常开发使用 sublime text 有好长一段时间了,有时候想拷贝当前正在编辑的文件名时发现没有很快捷的方法,一般都是先点击右键菜单栏中的 Reveal in Side Bar 对文件进行定位(在 ...

  3. sublime text 3 添加代码片段

    工具>插件开发>新建代码片段 <snippet> <content> <![CDATA[ Hello, ${1:this} is a ${2:snippet} ...

  4. [开发工具]_[Sublime Text 2]_[配置C++编译执行环境]

    场景: 1. g++的版本号更新比較快,能够使用最新的C++11特性, 这里sublime text 2是我的轻量级C++开发工具(仅仅有这个),能够使用st2这样的工具来验证一些语法新特性, 也能够 ...

  5. Sublime Text 修改默认语言为Python

    Sublime Text 3 修改默认语言为Python 步骤如下 英文:Tools - Developer - New Plugin 中文:工具 - 插件开发 - 新建插件 清空原来内容,用下面的代 ...

  6. Sublime Text 3中文乱码解决方法以及安装包管理器方法

    一般出现乱码是因为文本采用了GBK编码格式,Sublime Text默认不支持GBK编码. 安装包管理器 简单安装 使用Ctrl+`快捷键或者通过View->Show Console菜单打开命令 ...

  7. 在Sublime Text 3上安装代码格式化插件CodeFormatter

    1.了解CodeFormatter插件 在Sublime Text 3中编写代码,为了能让我们的代码格式变得漂亮整洁,需要一个能自动格式代码的插件.这里发现CodeFormatter插件不错,它能支持 ...

  8. sublime text 3 + python配置,完整搭建及常用插件安装

    四年的时间,一直使用EmEditor编辑器进行Python开发,之前是做面向过程,只需要将一个单独的py文件维护好即可,用着也挺顺手,但是最近在做面向对象的开发,不同的py文件中相互关联较多,感觉单纯 ...

  9. 前端工程师手中的Sublime Text

    原文地址:http://css-tricks.com/sublime-text-front-end-developers/ 我的Blog:http://cabbit.me/sublime-text-f ...

随机推荐

  1. 离线使用nuget

    先新建一个项目,将所有想保存下来或者要升级的package先安装或升级. 然后在项目中将packages文件夹全部拷贝出来,专门放到一个目录备用,以后的项目就可以根据此packages文件夹来离线使用 ...

  2. 我的android studio

  3. Linux的用户和用户组管理

      Linux是个多用户多任务的分时操作系统,所有一个要使用系统资源的用户都必须先向系统管理员申请一个账号,然后以这个账号的身份进入系统.用户的账号一方面能帮助系统管理员对使用系统的用户进行跟踪,并控 ...

  4. CSS随记

    在CSS中,任何元素都可以浮动.浮动元素会生成一个块级框,而不论它本身是何种元素.如果浮动非替换元素,则要指定一个明确的宽度:否则,它们会尽可能地窄. 注释:float属性不具有继承特性,就是说子元素 ...

  5. JAVA可变参数实例

    public class Kebiancanshu { public static void main(String[] args) { System.out.println(average(8, 2 ...

  6. poj3085

    #include <stdio.h> #include <stdlib.h> int main() { ; scanf("%d",&n); whil ...

  7. ubuntu gnome vnc

    1,安装登陆管理器 --apt-get install gdm (还可以为kdm/xdm) lightdm,display manager

  8. [C#]DataTable常用操作总结

    ].RowState == DataRowState.Unchanged)        {            //Your logic        }        //⑩Convert to ...

  9. STL的移动算法

    要在自己定义类型中使用移动算法.须要在元素中提供移动赋值运算符.移动赋值运算符和std::move()详见<c++高级编程>第9章 class mystring { public: str ...

  10. 自定义和扩展 SharePoint 2010 Server 功能区

    了解构成 SharePoint 2010 服务器功能区的组件以及如何通过演练两个功能区自定义项方案来自定义功能区. 适用范围: Microsoft SharePoint Foundation 2010 ...