您的位置:首页 > 其它

sublime text 创建自己的小插件的方法

2015-01-29 18:03 218 查看


转载自;http://mux.alimama.com/posts/541


前言:术语和参考资料

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直接安装了

(上面这段引用网络已有文章:点击打开链接,简短的插件开发入门也可以参考此文章。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: