1.1.9 扩展性和自动化
下面的章节展示了 Sublime Text 各种各样被扩展的附加功能。
指令
Sublime Text 中指令是无处不在的:按键绑定、菜单项、宏等都可以通过指令来工作。有些指令是在 Sublime Text 编辑器的核心实现的,但是他们当中的很多都是作为 Python 插件,每一个指令都可以由一个通过 Python 插 件来调用。
指令调度
通常情况,指令是被绑定到应用程序对象、一个窗口对象或是一个视图对象中。但是,窗口对象会根据输入的焦点来调度指令,所以你可以从一个窗口对象发送一个视图指令,它会为你找到正确的视图实例。
指令解析
指令都是以_
分隔的名称,如:hot_key
,它可以跟一个字典参数(key 是 string 类型,value 是 JSON 类型)。下面是几个从 Python 控制台运行的指令:
view.run_command("goto_line", {"line": 10})
view.run_command('insert_snippet', {"contents": "<$SELECTION>"})
view.window().run_command("prompt_select_project")
指令参考。
宏
宏是包含一个指令队列的自动化工具,可以用它来完成一些重复性工作。
宏文件是以.sublime-macro
为扩展名的 JSON 文件,Sublime Text 自带了几个提供核心功能的宏命令,如行和词的删除。你可以在工具 | Macros或是Packages/Default
中找到它们。
如何录制宏
按下Ctrl+q
,然后依次执行所需的命令。当操作完成时,再次按下Ctrl+q
结束宏的录制。新创建的宏不会保存成一个文件,只会保留在宏缓冲区中。现在你可以通过按下Ctrl+Shift+q
来运行这个宏,或者执行工具 | 保 存宏把它保存到一个文件中。
注意:宏缓冲区只会保留最新记录的宏,宏只会记录发送到缓冲区的命令:窗口级别的指令将会被忽视,如创建文件。
如何编辑宏
录制宏的另外一个选择就是手动编辑它,在Packages/User
下创建一个以.sublime-macro
为扩展名的文件,这个文件中写入操作指令。宏文件格式如下:
[
{ "command": "move_to", "args": { "to": "hardeol" } },
{ "command": "insert", "args": { "characters": "\n" } }
]
手动编辑宏时,需要对引号、空格和反斜杠前添加\
进行转义。
宏存储到位置
宏文件可以被存到任意包文件夹下,可以在工具 | 宏 | 处看到。
宏的按键绑定
可以通过组合键的形式运行宏,把宏文件的路径替换下面的run_macro_file
。
{ "keys": ["super+alt+l"], "command": "run_macro_file", "args": { "file": "res://Packages/User/Example.sublime-macro" } }
Snippets
我们在编写代码的时候,总会遇到一些需要反复使用的代码片段。这时候就需要反复的复制和黏贴,大大影响效率。用 snippet 功能可以使自己免于这种繁琐的操作。片段是能为你加入一些文字并使他们适应上下文的只能模板 。
文件格式
以.sublime-snippet为后缀的 XML 文件,例如,你可以把一个greeting.sublime-snippet
放到一个Email
的包里。
一个典型的 snippet 如下所示:
<snippet>
<content><![CDATA[Type your snippet here]]></content>
<!-- Optional: Tab trigger to activate the snippet -->
<tabTrigger>xyzzy</tabTrigger>
<!-- Optional: Scope the tab trigger will be active in -->
<scope>source.python</scope>
<!-- Optional: Description to show in the menu -->
<description>My Fancy Snippet</description>
</snippet>
snippet
元素包含了 Sublime Text 所需的一切信息从而判断应该插入什么内容、什么时候插入、是否需要插入。我们依次来看看这些组成部分。
content
真正的片段内容,片段可以是很简单的,也可以是非常复杂的。稍后会有各种示例。如果你要写自己的代码片段,请牢记下面的内容:
$
,你必须通过\$
的形式。- 始终用 Tab 来表示缩进,当代码片段被插入后,如果
translateTabsToSpaces
被设置为true
,Tab 键会被转化成空格符。 content
必须包含在<![CDATA[…]]>
块中,否则代码片段是无法生效的。- 代码片段的内容不能包含
]]>
,否则会提前结束<![CDATA[…]]>
,抛出一个 XML 的错误信息。你可以通过插入一个未定义的变量来避免这类问题,如:]]$NOT_DEFINED>
。Sublime Text 在插入代码段到文件之前会 把$NOT_DEFINED
替换成一个空字符串。
tabTrigger
定义必须被按下以插入此片断的键的顺序。输入此序列后按下 Tab 键,代码片段将会立即插入。 Tab 键是一个隐式的按键绑定。
scope
Scope 决定代码片段被激活的上下文,参考作用域获得更多内容。
description
当在 Snippets 菜单显示 snippet 时会用到,如果不存在,Sublime Text 将会默认显示代码段的文件名。
有了这些信息后,你就可以参照下节的描述写自己的代码段了。
Snippet 特征
环境变量
代码片段可以访问环境形式的上下文信息,下面列出的变量的值由 Sublime Text 自动设置。
你可以添加自己的变量来增加额外的信息,这些额外的变量是在.sublime-options
文件中定义的。
PARAM1 .. PARAMn | 传递给insert_snippet 命令的参数 |
---|---|
$SELECTION | 代码段被触发时所选中的文本 |
$TM_CURRENT_LINE | 代码段被触发时鼠标所在行 |
$TM_CURRENT_WORD | 代码段被触发时鼠标所在位置的单词 |
$TM_FILENAME | 被编辑的文件的名称,包括扩展名 |
$TM_FILEPATH | 被编辑的文件的路径 |
$TM_FULLNAME | 用户的用户名 |
$TM_LINE_INDEX | 代码段被插入的列,从 0 开始 |
$TM_LINE_NUMBER | 代码段被插入的行,从 1 开始 |
$TM_SELECTED_TEXT | $SELECTION的别名 |
$TM_SOFT_TABS | translate_tabs_to_spaces 值为 true 时是 true,否则为 false |
$TM_TAB_SIZE | 每个 Tab 表示的空格数(由tab_size 选项控制) |
看一个简单的例子:
USER NAME: $TM_FULLNAME
FILE NAME: $TM_FILENAME
TAB SIZE: $TM_TAB_SIZE
SOFT TABS: $TM_SOFT_TABS
=================================
# Output:
=============================
USER NAME: guillermo
FILE NAME: test.txt
TAB SIZE: 4
SOFT TABS: YES
=============================
字段(还是翻译成域?)
在字段标记的帮助下,你可以通过按 Tab 键在代码段中的位置进行跳转。字段被插入后,将会引导你完成一个代码段的定制。
First Name: $1
Second Name: $2
Address: $3
上面的例子中,当你按一次Tab
键光标将会调到$1
处,再次按Tab
键时会跳到$2
处,依次类推。你可以按下Shift+Tab
回到上一个位置,如果你按下了最多的Tab
,光标将会被定位到代码段的最后面,从而恢复到正常的 编辑状态。
如果你想控制什么时候退出,使用$0
标记,默认情况这是代码段的结尾。
按下Esc
键随时终止字段周期。
镜像字段
相同的字段标识互为镜像:当你编辑第一个时,其余的将会被实时填充为相同的值。
First Name: $1
Second Name: $2
Address: $3
User name: $1
示例中,“User name”将会被填充为和“First Name”相同的值。
Placeholder
对字段的语法扩展一点点就可以给字段添加默认值了。
First Name: ${1:Guillermo}
Second Name: ${2:López}
Address: ${3:Main Street 1234}
User name: $1
变量也可以被用来当做占位:
First Name: ${1:Guillermo}
Second Name: ${2:López}
Address: ${3:Main Street 1234}
User name: ${4:$TM_FULLNAME}
也可以使用嵌套的占位:
Test: {1:Nested {2:Placeholder}}
置换
除了占位语法外,制表符也可以通过置换指定更复杂的操作。
置换的语法如下:
${var_name/regex/format_string/}
${var_name/regex/format_string/options}
var_name
regex
Perl 风格的正则表达式:Boost library documentation for regular expressions.
format_string
Boost library documentation for format strings.
options
可选的,可以是下列的任意一种
i
不区分大小写。
g
全局替换。
m
允许多行匹配。
例如,可以毫不费力的给文字添加下划线:
Original: ${1:Hey, Joe!}
Transformation: ${1/./=/g}
# Output:
Original: Hey, Joe!
Transformation: =========
下面这个更复杂点的例子把下划线连接的词转化成首字母大写以空格分隔的词,基本上,它是结合了两个独立的表达式并且替换成一个。这也说明在 Tab 切换停止前替换就会发生。
Transformation: ${1/^(\w)|(?:_(\w))/(?1\u$1:)(?2 \u$2:)/g}
Original: ${1:text_in_snail_case}
# Output:
Transformation: Text In Snail Case
Original: text_in_snail_case
补全
本着 IDE 的精神,Sublime Text 通过捕获你所写的任何内容对汇总代码或内容进行提示,比如变量。
然而有多种方式扩展补全列表(例如,根据当前的语法)。
本文主题是如何使用补全?补全从何处来?
如何使用补全
有两种方法使用补全功能,两种方式优先级一样,但是所产生的结果是不同的。
补全内容可以用下列两种方式插入:
- 通过补全列表(
Ctrl + Spacebar
),或 - 按下
Tab
键
补全列表
使用补全列表的方法:
- 按下
Ctrl + Spacebar
,或者随便输入点内容。 - 你也可以再次按下
Ctrl + Spacebar
或使用上下箭头来选择下一个条目。 - 按下
Enter
或Tab
来验证选择(依赖于auto_complete_commit_on_tab
的设置)。 - 可选,重复按下
Tab
键以插入下一个可用的补全。
提示
此外,在补全列表的右侧可以看到一个提示,这是补全后的一个预览。
实际上上图中的结果将会展开成$arrayName = array('' => , );
的代码段。
触发条件和内容
来源于非当前文件中文字的补全将会提供一个不同于他们将要插入的内容的触发器,这通常用于函数的补全。
例如:PHP 中array_map
的补全将会得到array_map(callback, arr1)
的结果:
图中可以看到鼠标自动选择了callback
,这是因为补全提供了和代码段的字段和 placeholder 类似的功能。更多细节,请看Snippet 特征;
多行补全
Sublime Text 提供了同时进行多个补全的功能,但是每个光标当前位置及其前面的最后一个单词分隔符中的文本必须一致。
正确示例(|
代表光标):
l|
some text with l|
l| and.l|
错误示例:
l|
some text with la|
l| andl|
选择的内容实际上都是被忽视的,仅仅和光标所在位置有关。因此,e|[-some selection] example
(|
表示光标,[...]
表示所选文本)的补全结果是example|[-some selection] example
。
Tab
-完成补全
如果你想使用 Tab 键补全功能,必须把tab_completion
设置为true
(默认值),Snippet 补全不受此设置影响:它们始终依据自己的 tab 触发器来完成补全。
当tab_completion
补全可用时,都是自动完成补全的。也就是说,和补全列表不一样,Sublime Text 始终会帮你做一个决定。选择最佳补全规则如上所述,在有歧义的情况下,Sublime Text 将会使用它认为最合适的条目。你 可以多次按下Tab
键在其余选项中来回切换。
插入一个 Tab 占位符
当tab_completion
功能开启时,如果想要插入一个 Tab 占位符,可以按下Shift + Tab
。
补全及其优先级的相关资源
这些都是用户可控的补全来源,按优先级排序:
- Snippets
- 通过
on_query_completions()
的 API-注入式补全 - 补全文件
- 在缓冲区的词
指令面板
概述
命令面板是一个以执行命令为目的的可交互式的列表。
默认情况下,命令面板包含了许多有用的命令,并且提供了非常方便的个人设置和设置文件。
如何使用命令面板:
- 按下
Ctrl+Shift+P
- 选择一个命令面板
- 按下
Enter
命令面板通过上下文过滤条目,这就是说无论何时打开文件,你不会永远都看到定义在.sublime-commands
中的所有命令。
示例文件.sublime-commands
这是从Packages/Default/Default.sublime-commands
中的一段摘录:
[
{ "caption": "Project: Save As", "command": "save_project_as" },
{ "caption": "Project: Close", "command": "close_project" },
{ "caption": "Project: Add Folder", "command": "prompt_add_folder" },
{ "caption": "Preferences: Default File Settings", "command": "open_file", "args": { "file": "${packages}/Default/Base File.sublime-settings" } },
{ "caption": "Preferences: User File Settings", "command": "open_file", "args": { "file": "${packages}/User/Base File.sublime-settings" } },
{ "caption": "Preferences: Default Global Settings", "command": "open_file", "args": { "file": "${packages}/Default/Global.sublime-settings" } },
{ "caption": "Preferences: User Global Settings", "command": "open_file", "args": { "file": "${packages}/User/Global.sublime-settings" } },
{ "caption": "Preferences: Browse Packages", "command": "open_dir", "args": { "dir": "$packages" } }
]
语法定义
语法定义使得 Sublime Text 可以感知变成语言和标记语言。最明显的是它可以通过颜色来进行语法高亮。
先决条件
学习本章节,你必须安装 Sublime Text 的一个包PackageDev,这个包可以方便的进行新语法定义的创建。
文件格式
Sublime Text 使用属性列表(Plist)文件来存储语法定义,然而,由于编辑 XML 文件是一个繁重的工作,我们将使用 YAML 代替,之后把它转成 Plist 格式。这就是上面提 到的 PackageDev 包的用武之地。
注意:如果在本节内容的操作发生什么错误的话,很可能是 PackageDev 或是 YAML 有问题,不要马上认为是你的操作造成的错误,这也许只是 Sublime Text 的一个 bug。
如果你习惯了 XML,那么就手动编辑 Plist 文件,但是请一定注意有关于转义序列和 XML 标签等等时的不同需求。
作用域
作用域是 Sublime Text 中的核心概念,本质上讲,它是在缓冲区中有名字的文本区域。
例如,当你触发一个代码片段,Sublime Text 会检测绑定到代码段上的作用域并寻找插入符在文件中的位置。如果插入符当前的位置和代码段作用域匹配,则触发,否则无反应。
作用域可嵌套,从而可以支持高粒度作用域。你可以像 CSS 选择器一样进行深层次的钻取。例如,由于作用域选择器的存在,在 Python 源码中的单引号字符串中可以有一个按键绑定被激活,其他语言中则不会。
Sublime Text 借鉴了 TextMeat(Mac 下的一款文本编辑器)中有关作用域的想法,TextMate 的在线手册中包含很多有关作用域选择器的详细信息,这些内容对 Sublime Text 用户也是有很大帮助的。对于色彩的运用尤其明显 。
作用域和作用域选择器还是略有区别的:作用域是在语法定义中定义的名称,而作用域选择器的作用是在使用类似代码片段和按键绑定时触发作用域。创建一个新的语法定义的时候你关心的是作用域,当你想限制一个代码段到一 定的作用域范围时,你考虑的是作用域选择器。
语法定义是如何工作的
语法定义是和作用域名称配对的正则表达式的数组。Sublime Text 试图在一个缓冲区的文本中匹配这些模式,并为所有找到的附上相应作用域名称。这些正则表达式和作用域名称的配对被称为规则。
规则是按以下顺序应用:
- 规则在一行中的第一个位置被匹配上
- 数组当中第一条规则
每个规则都会占用被匹配的文字的区域,因此下一条规则尝试匹配时就会跳过这些已匹配上的(极少数情况例外)。
单独文件中的语法定义可以被组合,并且也可以将它们递归地应用。
着手语法定义
我们通过一个示例的方式来给 Sublime Text 代码段创建一个语法定义。我们只写真正的代码段内容,不会列出所有的.sublime-snippet
文件内容。
注意:由于语法定义主要用于语法高亮,我们将使用短语来划分源文件中的作用域。但是请牢记颜色和语法定义还不是一回事儿,作用域除了语法高亮外还有很多用处。
下面是我们想在一个代码片段中设计的元素:
- 变量 (
$PARAM1
,$USER_NAME
…) - 简单的 field(
$0
,$1
…) - 带有 placeholder 的复杂的 field(
${1:Hello}
) - 嵌套的 field
${1:Hello ${2:World}!}
) - 转移序列(
\\$
,\\<
…) - 非法序列(
$
,<
…)
下面这些对于本例来说太复杂所以我们不想放进去:
- 变量替换(
${1/Hello/Hi/g}
)
创建一个新的语法定义
按以下步骤执行:
- Tools | Packages | Package Development | New Syntax Definition
- 把文件保存成
.YAML-tmLanguage
文件放到Packages/User
文件夹下。
现在你可以看到一个如下的文件:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Syntax Name
scopeName: source.syntax_name
fileTypes: []
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
-
...
分别解释 key 的含义:
name
Sublime Text 将会展示在语法定义下拉列表中的名称,使用一个简短的带有描述性的名称。scopeName
本语法定义最顶层的作用域,格式为source.<lang_name>
或text.<lang_name>
。编程语言使用source
,标记语言或其他使用text
。fileTypes
这是文件拓展名列表(不包含前面的点)。当打开这些扩展名文件时,将会自动激活语法定义。uuid
语法定义的唯一识别码,每个新的语法定义都会有其 uuid,尽管 Sublime Text 会忽略它,千万不要编辑 uuid 的值。patterns
你的匹配模式的容器。
对于我们的例子,用下面的信息填充模板:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Sublime Snippet (Raw)
scopeName: source.ssraw
fileTypes: [ssraw]
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
-
...
注意:YAML 并不是一个严格的格式,然而不了解它的约定仍然会使你感到头痛。YAML 支持单引号和双引号,但是如果你在其中没有创建其他的 YAML 字面量时就可以忽略引号。如果这些约定对于 Plist 失败的话,在输出面板 查看一下有关出错的信息。稍后我们将解释如何把 YAML 中的语法定义转成 Plist,这也会出现在模板中的第一行注释中。
---
和 ...
是可选的。
分析模式
patterns
可以包含多种类型的元素,接下来的章节中我们会了解其中的一部分,如果你想了解更多,去查看 Textmate 的在线手册。
匹配项
匹配项的形式:
match: (?i:m)y \s+[Rr]egex
name: string.format
comment: This comment is optional.
match
Sublime Text 寻找匹配的正则表达式。name
任何match
被匹配到时的作用域名称。comment
可选的注释。
回到例子中,现在是这样的:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Sublime Snippet (Raw)
scopeName: source.ssraw
fileTypes: [ssraw]
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
-
...
确保patterns
是空的。
现在开始添加代码段的规则,先从简单的正则开始:
\$[0-9]+
# or...
\$\d+
我们可以建立如下的模式:
name: keyword.other.ssraw
match: \$\d+
comment: Tab stops like $1, $2...
也可以把它加到语法定义中:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Sublime Snippet (Raw)
scopeName: source.ssraw
fileTypes: [ssraw]
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
- comment: Tab stops like $1, $2...
name: keyword.other.ssraw
match: \$\d+
...
注意:对于 YAML 和上述例子中推荐用 2 个空格作为缩进。
现在,我们准备把文件转化成.tmLanguage
。处于兼容性考虑,语法定义使用了 Textmate 的.tmLanguage
作为扩展名。上面已经解释过,它是 Plist XML 文件。
请按照下列步骤来执行转换:
- 确保 Tools | Build System中的
Automatic
是勾选状态,或者选择Convert to …
- 按下
F7
- 将会在在相同的文件夹中生成一个新的
.tmLanguage
文件 - Sublime Text 将会刷新这些改动
如果你想知道为什么 PackageDev 知道你将要把文件转化成什么:请看第一行注释。
现在你已经创建了一个语法定义,下一步,创建一个.ssraw
文件,语法名将会自动切换成“Sublime Snippet (Raw)”,如果你输入$1
或是其他代码段字段就可以看到代码高亮效果了。
让我们继续创建环境变量的其他规则。
comment: Variables like $PARAM1, $TM_SELECTION...
name: keyword.other.ssraw
match: \$[A-Za-z][A-Za-z0-9_]+
重复以上步骤来更新.tmLanguage
文件。
Fine Tuning Matches
你可能已经注意到了,$PARAM1
中的所有文本有着相同的样式。你个人可能更倾向于让$变得明显,这就是captures
存在的意义,使用captures
,你就可以单独对它细分出一个匹配模式。
我们用captures
来修改一下上面的文件内容:
comment: Variables like $PARAM1, $TM_SELECTION...
name: keyword.other.ssraw
match: \$([A-Za-z][A-Za-z0-9_]+)
captures:
'1': {name: constant.numeric.ssraw}
capture 给你的规则带来了复杂性,但是它又是很简单的。需要注意数字是如何引用从左到右的带括号的分组。当然,捕获组你想写多少写多少。
注意:多亏了 PackageDev,在新的一行中输入1
按下 tab 键就会自动填充'1': {name: }
。
注:和正则表达式于置换一样,捕获中的0
应用于整个匹配。
开始-结束规则
现在,我们已经在使用一个简单的规则了。尽管我们已经见识过如何把规则模式分解成一个个小组件,但有时候你仍然想对那些源码中由明显的开始和结束标记分隔的大部分代码去应用规则。
用引号或其他划定结构的封闭文本字符串可以更好地处理开始-结束规则。
下面是这样的规则的一个框架:
name:
begin:
end:
下面是一个包含所有可选项的效果:
name:
contentName:
begin:
beginCaptures:
'0': {name: }
# ...
end:
endCaptures:
'0': {name: }
# ...
patterns:
- name:
match:
# ...
有些元素看起来很眼熟,但是它们组合起来就会让人望而生畏。我们来一个个看:
name
可选。整个匹配的作用域名称。实际上,这会给规则总定义的beginCaptures
,endCaptures
和patterns
创建嵌套作用域。contentName
可选,和name
不同,contentName
只会给闭合的文本添加一个作用域。begin
正则表达式,作用域开始的标识。end
正则表达式,作用域结束的标识。beginCaptures
可选,begin
标识的捕获,和简单匹配的捕获类似使用方法。endCaptures
可选,end
标识的捕获。patterns
可选,仅仅匹配始末的内容的一个匹配模式的数组,不匹配begin
和end
本身的文本。
段中定义复杂的领域:
name: variable.complex.ssraw
contentName: string.other.ssraw
begin: '(\$)(\{)([0-9]+):'
beginCaptures:
'1': {name: keyword.other.ssraw}
'3': {name: constant.numeric.ssraw}
end: \}
patterns:
- include: $self
- name: support.other.ssraw
match: .
这将是本教程中最复杂的模式,begin
和end
就不必解释了:他们定义了一个从${<NUMBER>:
到}
之间的区域。begin
的值必须用引号包起来,否则解析器发现有:
,又会识别成另外一个 key。beginCaptures
把开始标 记分割成更小的作用域。
最有意思的部分显然是partterns
。
上面已经看到了作用域是可嵌套的,为了解决这个问题,我们需要已递归风格进行作用域的嵌套。这就是它的价值:它对我们的开始-结束规则捕获的所有文字递归地应用整个语法定义。
注意:我们使用contentName: string.other.ssraw
代替上次的匹配模式,这种方式我们也提到过排序的重要性和已匹配上的将不会再次进行匹配等内容。
最后的完善
最后,来设置转义序列和非法序列的样式,然后就可以收工了。
- comment: Sequences like \$, \> and \<
name: constant.character.escape.ssraw
match: \\[$<>]
- comment: Unescaped and unmatched magic characters
name: invalid.illegal.ssraw
match: '[$<>]'
唯一的难点是[]
是在 YAML 中括起来的数组,需要放到引号中。除此之外,如果你熟悉正则表达式的话这些规则将会非常简单。
然而,在其他匹配到$
字符的规则后面,你必须非常小心地放置第二条规则,否则,由于前面被匹配到了导致后面的表达式都不会被匹配。
此外,即使添加了这两条额外规则,也要注意,上面我们的递归的开始-结束规则也会按预期继续工作。
这是最终的语法定义:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Sublime Snippet (Raw)
scopeName: source.ssraw
fileTypes: [ssraw]
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
- comment: Tab stops like $1, $2...
name: keyword.other.ssraw
match: \$(\d+)
captures:
'1': {name: constant.numeric.ssraw}
- comment: Variables like $PARAM1, $TM_SELECTION...
name: keyword.other.ssraw
match: \$([A-Za-z][A-Za-z0-9_]+)
captures:
'1': {name: constant.numeric.ssraw}
- name: variable.complex.ssraw
begin: '(\$)(\{)([0-9]+):'
beginCaptures:
'1': {name: keyword.other.ssraw}
'3': {name: constant.numeric.ssraw}
end: \}
patterns:
- include: $self
- name: support.other.ssraw
match: .
- comment: Sequences like \$, \> and \<
name: constant.character.escape.ssraw
match: \\[$<>]
- comment: Unescaped and unmatched magic characters
name: invalid.illegal.ssraw
match: '[$<>]'
...
还有更多的使用“repository”进行结构和代码的重用的技术,但上述的解释只是让你着手于语法定义。
注意:由于 PackageDev 是向后兼容的,所以如果你以前使用 JSON 语法定义,现在仍然可以做到这一点。
插件
本部分适用于有编程能力的用户。
Sublime Text 可以通过 Python 插件进行扩展,插件通过重用现有命令或创建新命令来构建功能。插件是逻辑实体而非物理实体。
先决条件
你必须掌握 Python 编程才能进行插件的开发。
插件存储位置
Sublime Text 只会在下列位置寻找插件:
Installed Packages
(只有 .sublime-package文件)Packages
Packages/<pkg_name>/
因此,任何嵌套在更深层次的包都不会被加载。
不提倡直接把插件放到 Packages
下,Sublime Text 在加载它们之前会以一个预先定义好的规则对其进行排序,所以如果直接放到 Packages
下可能会得到令你困惑的结果。
第一个插件
给 Sublime Text 写一个“Hello, World!”插件:
- 在菜单栏中选择 Tools | New Plugin…。
- 保存为
Packages/User/hello_world.py
.
你已经写了你的第一个插件,
- 创建一个新的缓冲区(
Ctrl+n
)。 - 打开 Python 控制台(`Ctrl+``)。
- 输入
view.run_command("example")
然后按下回车。
新的缓冲区(其实就是未保存的新文件)中将会看到“Hello, World!”。
分析你的插件
上面的插件看起来就是这样的:
import sublime, sublime_plugin
class ExampleCommand(sublime_plugin.TextCommand):
def run(self, edit):
self.view.insert(edit, 0, "Hello, World!")
Sublime Text 提供了 sublime
和 sublime_plugin
模块;它们不是 Python 的标准库。
前面已经说过,插件创建或是重用命令,命令是 Sublime Text 的重要组成部分,它只是可以被 Sublime Text 中不同的方式如插件 API、菜单文件、宏等等调用的 Python 类。
Sublime Text 命令是从定义在sublime_plugin
中的 *Command
类派生出来的(后面会作介绍)。
例子中余下部分的代码涉及到TextCommand
或是 API 的,在后面的章节再讲。
在接着讲之前,我们来看看如何调用一个新的命令:首先打开 Python 控制台,然后下发一个命令调用view.run_command()
。这种调用命令的方式相当的不方便,但是对于开发阶段开始很有用的。现在请记住,你的命令可以像 其他命令一样通过快捷键或是其他方式呗调用。
命令名称的约定
你也许已经注意到我们的命令命名为 ExampleCommand
,但是我们却是通过传递字符串example
给 API 进行调用。Sublime Text 标准的命令命名方式就是把类名的Command
后缀剥离,然后把 PhrasesLikeThis
这样的用下 划线进行连接,就像:phrases_like_this
。
新的命令也应该遵循这种命名方式。
指令的类型
你可以创建下列类型的命令:
- 窗口命令(
sublime_plugin.WindowCommand
) - 文本命令(
sublime_plugin.TextCommand
)
根据你的目的选择合适的命令类型。
命令的共同点
所有的命令都需要实现一个.run()
函数才能正常运行,此外,它们可以接收任意多的关键字参数。
注意:命令的参数必须是有效的 JSON 值。
窗口命令
窗口命令是在窗口级别进行操作,这并不是说你不能从窗口命令处理视图,而是说你使用窗口命令时无需视图。如,正在开发的 new_file
是一个窗口命令,所以你不打开任何视图就可以调用这个命令。
窗口命令实例中有.window
属性指向创建它们的窗口实例。
窗口命令的 .run()
函数不需要任何位置参数。
文本命令
文本命令是在视图级别进行操作,所以调用时必须得有一个视图。
文本命令实例中有.view
属性指向创建它们的视图实例。
文本命令的.run()
函数需要一个edit
实例作为其第一个参数。
文本命令和edit
对象
edit
对象把视图的编辑尽心分组以便撤销和宏可以正常工作。
注意:和老版本不同,Sublime Text3 不允许对编辑对象进行编程控制。API 是负责管理其生命周期的。插件的作者必须确保新的文本命令中的所有的编辑都在.run()
中。要调用现有命令,你可以使 用view.run_command(<cmd_name>,<args>)
或类似的 API 调用。
事件的响应
任何从EventListener
派生的命令都能够对事件作出响应。
另一个插件实例:
让我们创建一个从谷歌的自动填充服务获取数据的插件,然后将其送至 Sublime Text 补全列表。
import sublime, sublime_plugin
from xml.etree import ElementTree as ET
import urllib
GOOGLE_AC = r"http://google.com/complete/search?output=toolbar&q=%s"
class GoogleAutocomplete(sublime_plugin.EventListener):
def on_query_completions(self, view, prefix, locations):
elements = ET.parse(
urllib.request.urlopen(GOOGLE_AC % prefix)
).getroot().findall("./CompleteSuggestion/suggestion")
sugs = [(x.attrib["data"],) * 2 for x in elements]
return sugs
注意:请确保学习后把这个示例插件删掉,否则它会影响到系统默认的自动补全。
学习 API
想创建插件,你需要熟悉 Sublime Text 的 API 和命令,在写这个教程时有关这两个的文档还很少,但你可以通过现有代码来学习。
Packages/Default 包含很多无文档的命令和 API 调用的例子。如果你想看它的代码,必须先把其内容提取到一个文件夹中。作为练习,你可以创建一个构建系统来按需做那样的操作,才能够方便的查看项目的示例代码。
包
概述
包是资源的容器。
包的位置(和缩写)
有三个位置存储包,每个地方都有不同的作用:
- 包可以是
Data/Packages
下的文件夹(简写成Packages
) - 位于
Data/Installed Packages
(简写成Installed Packages
)或其任意子目录下的以.sublime-package
为后缀名的 zip 压缩包 。 Application/Packages
(简写成:Shipped Packages
)下有一些默认的 zip 压缩包,Application
是 Sublime Text 可执行文件所在的目录。
注:为了简洁,我们把以上所有的路径统称为 Packages,任何文件夹下(不管是不是.sublime-package
)的包称之为 Packages/PackageName
。因此,包中的文件会被称为 PackageName/a_file.extension
。
.sublime-package
包
作为 .sublime-package
压缩文件应该是只读的,永远不能被手动修改。这是由于它通常是作为一个整体被更新的,任何手动修改的内容都将在更新过程丢失。
如果你想修改它们,查看定制化或覆盖包。
同名包的相互影响
如果Installed Packages
和Shipped Packages
中存在两个同名包,将会用Installed Packages
中的这个,自带包将会被忽略。
Packages/PackageName
中所有的文件都优先于Installed Packages/PackageName.sublime-package
和Shipped Packages/PackageName.sublime-package
中的同名文件。
参看定制化或覆盖包。
包的内容
包中的典型资源有:
- 构建系统(
.sublime-build
) - 颜色主题(
.tmTheme
) - key maps (
.sublime-keymap
) - 宏(
.sublime-macro
) - 菜单(
.sublime-menu
) - 元数据(
.tmPreferences
) - mouse maps (
.sublime-mousemap
) - 插件(
.py
) - 设置(
.sublime-settings
) - 代码段(
.sublime-snippet
) - 语法定义(
.tmLanguage
) - 主题
.sublime-theme
)
一些包是用来支持其他包或核心功能的,如,英文字典拼写检查器使用Installed Packages/Language - English.sublime-package
作为数据存储。
包的类型
本指南中,讨论这个主题时为了方便我们把包进行了分类,但是 Sublime Text 不适用这些术语,所以你不必了解它。
默认包
Sublime Text 自带的包,一些是核心包,其他的则是对 Sublime Text 进行增强的包。
例如:Default, Python, Java, C++, Markdown.
位于 Shipped Packages
下。
核心包
Sublime Text 正常工作的必需包。
完整的清单:Default, Theme - Default, Color Scheme - Default, Text, Language - English。
这是自带包的一部分,位于位于 Shipped Packages
下。
用户包
用户安装的包,这通常是用户自己或是第三方开发的包。
已安装的包
用户包的一个分支,是由包管理者进行维护的 .sublime-package
压缩文件。位于Installed Packages
。
注:由于 Sublime Text 中这个文件夹的名称有点不合适,所以当我们说到安装包时可能会令人感到困惑。
在本指南中,有时候说安装是给 Sublime Text 添加一个第三方包,有时候是指复制一个.sublime-package
文件到Installed Packages
下。
覆盖包
用户包的一种特殊类型。它是对发布成.sublime-package
文件尽心定制化后的包。位于 Packages
下。
管理包
安装包
注:由于有自动包管理器,所以普通用户几乎不需要去了解如何手动安装包。
Sublime Text 的包管理器是 Package Control。
包的安装通常采用以下两种主要方式:
- 复制 Sublime Text 资源到
Packages
- 复制
.sublime-package
文件到Installed Packages
。
禁用包
把包名加入到Packages/User/Preferences.sublime-settings
中的ignored_packages
以临时禁用包。
启用包
从Packages/User/Preferences.sublime-settings
中的ignored_packages
中删除要启用的包名即可。
删除包
如果你是通过包管理器安装的,那么使用包管理器的默认卸载方法。
如果你是手动安装的包,那么按一下步骤来安全删除一个包:
- Sublime Text 打开时先禁用包。
- 关闭 Sublime Text。
- 从硬盘中删除包的资源。
- 从
ignored_packages
中移除包名。
除了最开始位于 Packages
和Installed Packages
中的资源,插件还会创建其他文件(例如.sublime-settings
文件)来存储有关包的相关信息。 通常情况下,你可以在User package 下找到这些文件。因此,如果你 想彻底卸载包,还需要找到这些额外的文件一并删除。
Sublime Text 更新时会把所有自带的包复原,所以自带包是无法彻底删除的,如果你想停止使用自带包,直接禁用就行。
定制化或覆盖包
.sublime-package
是只读的,所以你无法直接编辑它。但是,Sublime Text 允许你创建覆盖包,这无需修改原始存档就可以有效注入到原始存档中。
创建覆盖包:在Packages
下创建一个新的文件夹,以你要修改的.sublime-package
文件的名称命名,不带后缀。在此包中创建的任何文件,将优先于原包中的同名文件。
覆盖包中的文件是全部替换原始文件,所以相应的.sublime-package
中的文件更新是你可能注意不到。
合并与优先顺序
包的优先级对于合并某些资源来说非常重要。
如果存在一个 .sublime-package
的覆盖包,它将和.sublime-package
同时加载。
Sublime Text 加载包的顺序如下:
Packages/Default
;- 默认包,按字典顺序;
- 已安装的包,按字典顺序;
- 除
Packages/User
外所有用户包,按字典顺序; Packages/User
还原 Sublime Text 为默认配置
把 Sublime Text 恢复到初始状态可以解决 Sublime Text 的一些错误(然而这些错误通常是由于插件造成的)。
把 Sublime Text 恢复到默认状态并删除所有的个人配置,删除 packages 目录并重启编辑器。Installed Packages
会被一并删除,所以你安装的插件都宣告拜拜了。
在做这种操作之前务必备份数据。