zoukankan      html  css  js  c++  java
  • sublime text 插件开发

    前言:术语和参考资料

    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的类。我们先保存下并运行下试试。
    保存的时候要创建一个包。保存弹出框默认是在PackagesUser目录下,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(' ', ' ').replace(' ', ' ') 
        line_endings = self.view.settings().get('default_line_ending') 
        if line_endings == 'windows': 
            string = string.replace(' ', ' ') 
        elif line_endings == 'mac': 
            string = string.replace(' ', ' ') 
        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 = ' '
    if use_spaces: 
        indent_characters = ' ' * tab_size 
    prefixed = prefixed.replace(' ', ' ' + indent + indent_characters)
     
    用开头的空白来判断下新插入的CSS代码位置是否正确。
    match = re.search('^(s*)', original) 
    prefix = match.groups()[0] 
    match = re.search('(s*)', 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,简短的插件开发入门也可以参考此文章。)
     
     
  • 相关阅读:
    Linux查看CPU信息
    sed总结
    angularjs学习笔记--service、$http、$scope、angularjs指令、事件、api
    angularjs学习笔记--ng-model、controller、DI、$scope、$provide
    angularjs学习笔记--$http、数据渲染到表格、路由、依赖注入、provider
    angularjs学习笔记--主html&template html&module&template js、模块、控制器、双向数据绑定、过滤器
    angularjs学习笔记--视图、模板、组件、$http、$q、module
    angular学习笔记---下载并运行nicefish项目
    angular学习笔记---通过angular/cli建一个新的项目
    json模拟数据交互
  • 原文地址:https://www.cnblogs.com/rsky/p/4569109.html
Copyright © 2011-2022 走看看