因为工作的原因,会接触到公司自己开发的自动化语言,但是这个语言从开发到现在,一直没有一个匹配的IDE(其实是有一个网页版的IDE,但是不好用没人用),而一直使用Notepad++,他们习惯了,但是我作为公司新人有点忍不了界面丑陋,没有高亮,不能快速折叠函数等等功能缺陷,所以自己看了看Sublime的插件指引,自己做了一个IDE适配自动化语言,特记录如何开发sublime插件。
文章介绍的并不全面,主要还是“领进门”,重在制作的方法。(因为自己也没有很懂😛)
插件支持的功能
- 自定义语言
- 主题(就是配色啥的)(该文章不介绍)
- 添加菜单
- 命令
- 自定义快捷键
命令/快捷键/菜单
这几个功能其实是相辅相成,共同使用的。
逻辑是:键盘按下一个组合件,触发命令,执行操作;或者在菜单中添加一个选项,点击这个选项来触发命令,执行操作。
我们先来一个简单的示例说明怎么做
示例1:添加一个上下文菜单,取名为“print hi”,当点击这个菜单时,在当前文件的起始位置打印hi
点击Tools -> Developer -> New Plugin 会新建一个python文件,并显示一个Demo
import sublime
import sublime_plugin
class ExampleCommand(sublime_plugin.TextCommand):
def run(self, edit):
self.view.insert(edit, 0, "Hello, World!")
可以看到这里有一个名为ExampleCommand 类,重载了里面的run 方法,方法中这个语句的含义是在文本的最开头位置插入"Hello World"文本,其中Command前面的Example就是命令的名称。
Command前面第一个大驼峰单词就是命令。比如PrintHiCommand这个类名,print就是命令的名称。命令这个概念我们后面就慢慢清楚了。
我们修改命令名为“printhi”,修改文本为"hi"。程序长这个样子:
import sublime
import sublime_plugin
class PrintHiCommand(sublime_plugin.TextCommand):
def run(self, edit):
self.view.insert(edit, 0, "hi")
ctrl+s保存一下,弹出保存对话框的路径自动就是插件所存在的地方,默认是**\Pakages\Users,所有的插件包都存放在Pakages文件夹下,你可以叫任意的名字。这里我们自己建立一个名为TestPackage的插件文件夹放置我们所有的插件文件。
所有的插件包都存放在Pakages文件夹下 安装板sublime存放路径: C:\Users[用户名]\AppData\Roaming\Sublime Text\Packages 绿色版sublime存放路径: [sublime根目录]\Data\Packages\
这样我们第一个命令就做好了。
接下来,在这个文件夹下新建一个文件,取名为Context.sublime-menu,以json格式,写入以下内容
Context.sublime-menu。后缀表示该文件是定义菜单文件,Context文件名表示这个菜单将插入到上下文档中。
[
{
"caption": "hi",
"command": "print",
"id": "hi"
}
]
caption表示菜单中的名称 command表示当选项被点击后,所触发的命令 id表示这个菜单项的唯一标识
保存文件,不用退出sublime,立刻可以看到效果。点击鼠标右键,看到最下面出现了名为hi的选项,点击可以看到文件的最前面输入了hi。 恭喜你,第一个插件已经完成了。
我用官方回答你的废话
开发插件怎么可能少了官方文档。 官方文档 上面就是sublime的文档地址。如果地址变动,也可以直接打开sublime官网官网地址,点击右上角Support,可以看到Documentation一栏,下面有很多指引,如直接打开documentation,或是官方论坛(不用翻墙可以访问),油管频道等。 我们重点介绍以下几个部分 有了前面的例子,相信大家已经知道如何编写插件,更多的功能自己看文档解决,毕竟我也没有精力写的很全。后文只会对文档中比较绕的名词和概念,以及我踩的坑做一说明。 那些英语不好的,右转各种翻译插件,谢谢。
菜单 Menu
-
只有caption和command是必要的,其他可写可不写。 -
其中mnemonic助记符,我也没有搞得很明白,如果有谁知道这个选项是干嘛的,烦请评论告诉大家,我会给你一个nubiplus。 文档中只说了是一个按下一个按键可以激活进入入口,并且要和caption对应,也就是对应caption的第一个字母。我猜想是按下键盘的alt键,再组合按下这个助记符,可以激活指定的命令,就像直接鼠标点按菜单一样。 -
Side Bar Mount Point.sublime-menu 由于默认sublime隐藏侧边栏,我也是才知道这家伙还有个侧边栏。在View -> Side Bar -> Show Side Bar就可以显示侧边栏。 上面的文件用于添加侧边栏顶部右键菜单。
Tips
-
可以带参
API
API Reference python的文件名没有那么讲究,叫啥都行。
从前面的示例可以看到,我们自定义的类继承了sublime_plugin.TextCommand类。从官方的文档中也可以看到这个类的说明,我们重写了run()函数,当命令执行时,这个函数将会被调用,并执行里面的语句。
如果我们直接在函数中print(),打印的信息将会出现在哪里呢? 会在sublime的控制台当中。这一点非常重要,我们DEBUG经常会用到。使用快捷键ctrl+`,可以打开sublime的命令行。 此时,执行一下命令,可以看到输出。
import sublime
import sublime_plugin
class ExampleCommand(sublime_plugin.TextCommand):
def run(self, edit):
print("Hello World!")
下面会对部分类做一个简要说明:
-
sublime.View 表示所打开文件的类。如在sublime中打开test.txt,那么view就代表这个test.txt对象。 比如,关闭这个标签(其实也就是关闭这个文件) import sublime
import sublime_plugin
class ExampleCommand(sublime_plugin.TextCommand):
def run(self, edit):
self.view.close()
从上面这个例子可以知道,sublime.View是sublime_plugin.TextCommand类的父类。 view类还有很多函数,如sel()可以返回一段选中的范围、line()可以根据一个范围或点返回所在行,大家可以自己摸索。 -
sublime.Region 表示一个范围。 非常简单容易理解。注意,该类是可以直接根据头尾构造的。 -
sublime.Selection 表示选中的对象。 如果使用add()函数,将多个region添加进Selection对象中,就是多选。
自定义语言
我把这个放在了后面,主要是因为自定义语言这个功能应用场景应该比较少。 使用.sublime-syntax 作为后缀名,对文件名没有要求,但是推荐以自定义的语言名作为名称。 该文件使用yaml语法,推荐先去熟悉一下什么是yaml语法。yaml语法 参考syntax官方说明文档 我们后文都以c语言做范例,先编写一个简单的Demo:
%YAML 1.2
---
name: C
file_extensions: [c, h]
scope: source.c
contexts:
main:
- match: \b(if|else|for|while)\b
scope: keyword.control.c
name:语言名 file_extensions:所定义语言的拓展名 scope:应用范围(这个概念后面讲) main:主上下文分支 match:匹配文本
原理
自定义语言最终的目的是解析语法,也就是让sublime根据文本特征,知道这段文字所表达的含义。 文本特征比如,c语言使用函数时,一定是函数名+括号的形式,根据这个特点,sublime就知道这里应该是一个函数,应该使用怎样的颜色高亮,除此之外,还可以知道这个函数的定义在哪。 我们在sublime-syntax文件中做的,就是定义文本特征,让sublime知道哪里是什么意思。 定义特征,使用的方法就是正则表达式,所以必须对正则非常熟悉。正则表达式
了解了上面的原理,我们需要对match和scope两个重要的概念做一个诠释。
有了上面基础还不够,还需要解决一个问题。类似函数体、函数当中的参数这样有“包裹结构”的,或者说层级结构的,如何表达。 比如,有下面一个例子。
int funcDemo(int a, int b)
{
if (a == b)
{
return a * 2;
} else {
return a + b;
}
}
这里函数声明funcDemo,后面跟着形参,那么两个形参就是一个层级;再往后,是一对{} 包裹的函数体,大括号中的所有内容都是属于funcDemo的函数体,那么函数体又是一个层级,依次向下推。。。 我们前面介绍过了,match是从前到后的匹配文本。那么文本就可以看作栈,当遇到funcDemo( 第一个左括号时,可以看作是将后文的所有内容“进栈”,直到遇到了) 右括号才将后文所有内容出栈,这样int a, int b 就可以有办法表示为函数参数。函数体也同理;if、while等这样的语句也同理。 sublime中使用push和pop来进栈和出栈。
main:
- match: '(?<=\w+) *\('
scope: punctuation.section.group.begin.c
push: function_paramter
function_paramter:
- scope: meta.group.c
- match: \b(int|float|double|char)\b
scope: storage.type.c
- match: \)
scope: punctuation.section.group.end.c
pop: true
从上面的示例可以看出,push可以将文本入栈到指定的yaml对象中解析,入栈的这一部分内容,会完全按照新的规则解析,直到遇到了pop,如果pop为true,则出栈到外面一层。 如果存在多层嵌套,就需要一层一层push再一层一层pop,直到根。这也符合c语言以及大多数语言的规定。 还有一个小细节非常值得注意,在function_paramter对象中,在第一个位置有十分孤单的scope并没有任何match与之对应scope: meta.group.c ,其实这就是function_paramter的scope,表示这个对象的含义,一般以meta打头。
那么我们就明白了,最开始的示例中,最开头的scope,scope: source.c 其实就是表示这个语言的根scope。
让我们开始吧!
懂得了上面的原理,做起来就非常轻松了。 官方文档中列举有几乎所有支持的scope定义scope naming文档
tips: 对于我们的自定义语言,最快速有效的开发方式就是对照别的语言来开发。使用快捷键ctrl+shift+alt+p 快速查看文本定义。 比如对照着js语言,我们不知道function关键字会给一个怎样的scope,我们可以将光标移动到function上面,使用快捷键查看。 结合前文讲到的,scope是会根据不断的push、pop形成一个类似树状的结构,上面列出的就是按照从根到叶子的所有scope
除了刚刚介绍的push、pop以外,还有set可以直接把后文推到某个层级当中。 大致就是这样了,后面有哪里想起来需要添加的再添加进去。
|