sublime text3插件开发例程
sublime使用python编写插件,安装好sublime后,就自带了2个库sublime,sublime_plugin,基于这2个库,我们可以开发许多插件,以下就是一个简单的入门实例。
API介绍在API Reference
官方教程在How to Create a Sublime Text 2 Plugin | Envato Tuts+
1.推荐一个截图软件Snipaste
2.打开tools->developer->new plugin
3.保存生成的文件xxx.py到Packages下,新建一个文件夹,自定义名称。
4.sublime会搜索packages下的py文件,用ctrl + ` 打开控制面板。输入view.run_command('example'),就可以看到文本中首行插入了Hello, World!,即执行了 self.view.insert(edit, 0, "Hello, World!")
5.Example就是这个类class的名称,用run command运行它的时候,就执行run这个函数下的指令(大概是这个意思)。一个py文件下可以有多个class,1个class下只有1个run入口。其他教程也有说明,Command前面的字段是命令名称,按大写字母分段,调用时都使用小写。也就是说Command前面可以写Example和example,但不能写ExaMple。
6.self.view.insert(edit, 0, "Hello, World!") 在API手册中看到,
insert(edit, point, string) | int | Inserts the given string in the buffer at the specified point. Returns the number of characters inserted: this may be different if tabs are being translated into spaces in the current buffer. |
在指定point的位置插入后面string。
7.接下来我要实现在文本中选中一段注释改变成下面这个样式。右键点击快捷方式,或按ctrl + 1 插入分割线。如下图的效果。
8.首先要找到当前光标的位置,取出要的字符串,插入cutline,将字符串插入进去。
import sublime
import sublime_plugin
import re
import math
class AddcutlineCommand(sublime_plugin.TextCommand):
def run(self, edit):
region = self.view.sel()[0] #获取当前选取的区域,a = self.view.sel()[0].a 获取当前区域的起始point
str_a = self.view.substr(region) #获取当前选择区域字符串
parameter = sublime.load_settings("AddCutLine.sublime-settings")
LEN = parameter.get('LEN')
print(LEN)
if len(str_a)==0: #如果仅是光标的位置,从该位置的那一行顶行插入cutline
region=self.view.line(region) #获取从行开始的region
point = region.a
if LEN == "long":
self.view.insert(edit, point, "//=======================================================================================================================================================\n") #打印时显示在最底下
self.view.insert(edit, point, "//---------------------------------------------------------------------- Text Here ----------------------------------------------------------------------\n")
self.view.insert(edit, point, "//=======================================================================================================================================================\n")
elif LEN == "short":
self.view.insert(edit, point, "//===========================================================================\n") #打印时显示在最底下
self.view.insert(edit, point, "//-------------------------------- Text Here --------------------------------\n")
self.view.insert(edit, point, "//===========================================================================\n")
else:
print(str_a)
region=self.view.line(region)
point = region.a
print(region)
str_a = self.view.substr(region)
print(str_a)
str = re.findall(r"[/#]+\s*(.*\S+)\s*$", str_a) #取得//或者#后面的字符串,去除前后空格
srt_len =len(str[0])
print(srt_len)
print(str)
if LEN == "short":
self.view.erase(edit,region)
self.view.insert(edit, point, "//===========================================================================\n") #打印时显示在最底下
self.view.insert(edit, point, "//---------------------------------------------------------------------------\n")
self.view.insert(edit, point, "//===========================================================================\n")
first_line_end = self.view.line(point).b #获取当前行的region (1739, 1816) 起始point
print(first_line_end)
#计算替换字符串的偏移量
start_p = first_line_end+round((75-srt_len)/2)+3
r1 = sublime.Region(start_p,start_p+srt_len)
print(round((75-srt_len)/2))
self.view.replace(edit, r1, str[0]) #将该区域替换为原来的注释
elif LEN == "long":
self.view.erase(edit,region)
self.view.insert(edit, point, "//=======================================================================================================================================================\n") #打印时显示在最底下
self.view.insert(edit, point, "//-------------------------------------------------------------------------------------------------------------------------------------------------------\n")
self.view.insert(edit, point, "//=======================================================================================================================================================\n")
first_line_end = self.view.line(point).b #获取当前行的region (1739, 1816) 起始point
print(first_line_end)
#计算替换字符串的偏移量
start_p = first_line_end+round((151-srt_len)/2)+3
r1 = sublime.Region(start_p,start_p+srt_len)
print(round((151-srt_len)/2))
self.view.replace(edit, r1, str[0]) #将该区域替换为原来的注释
9.代码编写完成后,设置快捷键和文本右键快捷方式。拷贝其他插件的2个文件到py目录。
Context.sublime-menu是在文本编辑界面中的右键菜单,keymap是设置快捷键的。还是side bar是设置右边文件栏的右键菜单,还没有搞过,原理一样的。
Context.sublime-menu:
[
{ "caption": "-", "id": "addcutline" }, //再建一个右键菜单中的分段。可以没有。id是唯一标识
{ "command": "addcutline", "caption": "AddCutLine" }, //command就是具体要执行的命令,caption是显示的名称
]
Default (Windows).sublime-keymap:
[
{ "keys": ["ctrl+1"], "command": "addcutline"},
]
AddCutLine.sublime-settings: 设置参数,在py文件中可以引用里面的设置
{
//long or short
"LEN":"short"
}
Main.sublime-menu:在主菜单中显示相关设置和read me信息。
[
{
"caption": "Preferences",
"mnemonic": "n",
"id": "preferences",
"children":
[
{
"caption": "Package Settings",
"mnemonic": "P",
"id": "package-settings",
"children":
[
{
"caption": "AddCutLine",
"children":
[
{
"command": "open_file",
"args": {"file": "${packages}/AddCutLine/README.md"},
"caption": "README"
},
{ "caption": "-" },
{
"caption": "Settings",
"command": "edit_settings",
"args": {
"base_file": "${packages}/AddCutLine/AddCutLine.sublime-settings"
}
}
]
}
]
}
]
}
]
//===========================================================================
//----------------------------------Read Me----------------------------------
//===========================================================================
#选择一行已经被//或者#注释的语句,右键选择AddCutLine或者Ctrl+1快捷键,会快速将该注释格式化成上面 Read Me所示。
#注意,如果有中文,中间的右侧会多出来一些。
以上操作后,完整的一个插件就诞生了。有关python和正则表达式的学习请同学们移步其他教程,本文简单介绍如何编写sublime text3的插件。
//===========================================================================
//----------------------------------Thanks-----------------------------------
//===========================================================================
以此文感谢niechuan老师在我python和正则表达式前行路上的帮助,祝nie老师永垂不朽!
10.为sublime的Verilog Gadget插件增加2个功能,Copy port to MarkDown和Copy port to Xlsx
前者很好操作,Verilog Gadget/core/vgcore.py中增加一个VerilogGadgetPortToMarkdown的类,run函数下照抄前面VerilogGadgetModuleInst中的代码:
def portMarkdown_inst(mod_name, port_list, param_list):
name_lmax = 0
width_lmax = 0
prmonly_list = []
for _strl in param_list:
if _strl[0] == 'parameter':
width_lmax = max(len(_strl[1]),width_lmax)
name_lmax = max(len(_strl[2]),name_lmax)
prmonly_list.append(_strl)
for _strl in port_list:
width_lmax = max(len(_strl[1]),width_lmax)
name_lmax = max(len(_strl[2]),name_lmax)
sp0 = name_lmax +2-len("Name")
sp1 = width_lmax+2-len("Width")
sp2 = len("output")+2-len("Dir")
sp3 = 60+2-len("Description")
string = ""
string = string + "| Name" + " " * sp0 + "| Width" + " " * sp1 + "| Dir" + " " * sp2 + "| Description" + " " * sp3 + "| other |"+"\n"
string = string + "| ----" + "-" * sp0 + "| -----" + "-" * sp1 + "| ---" + "-" * sp2 + "| -----------" + "-" * sp3 + "|-------|"+"\n"
for i, _strl in enumerate(prmonly_list):
sp0 = name_lmax +2-len(_strl[2])
sp1 = width_lmax+2-len(_strl[1])
sp2 = 8-len("Dir")
sp3 = 60+2-len(_strl[3])
string = string + "| "+_strl[2]+" "*sp0+"| "+_strl[1]+" "*sp1+"| " +" "*sp2+"| "+_strl[3]+" "*sp3+"| |"+"\n"
for i, _strl in enumerate(port_list):
sp0 = name_lmax +2-len(_strl[2])
sp1 = width_lmax+2-len(_strl[1])
sp2 = 8-len(_strl[0])
sp3 = 60+2
string = string + "| "+_strl[2]+" "*sp0+"| "+_strl[1]+" "*sp1+"| "+_strl[0]+" "*sp2+"| "+" "*sp3+"| |"+"\n"
return string
class VerilogGadgetPortToMarkdown(sublime_plugin.TextCommand):
def run(self, edit):
if not check_ext(self.view.file_name(), self.view.name()):
return
text = self.view.substr(sublime.Region(0, self.view.size()))
text = remove_comment_line_space(text)
module, ports_list, param_list, clk_list, rst_list = parse_module(text, 'Instantiate Module')
if not module:
return
vgs = get_prefs()
iprefix = vgs.get("inst_prefix", "inst_")
print(module)
print(ports_list)
print(param_list)
minst = portMarkdown_inst(module, ports_list, param_list)
sublime.set_clipboard(minst)
disp_msg("PortToMarkdown Module : Copied to Clipboard")
解析完代码后,自己再写一个函数,写成markdown格式即可。最终生成如下内容到粘贴板。
| Name | Width | Dir | Description | other |
| ----------| -------------| --------| --------------------------------------------------------------|-------|
| DATAW | [3:0] | | 32 | |
| ADDRW | | | 5 | |
| AFULL | | | 5 | |
| wrclk | | input | | |
| wrst_n | | input | | |
| rdclk | | input | | |
| rrst_n | | input | | |
| wren | | input | | |
| wdata | [DATAW-1:0] | input | | |
| full | | output | | |
| afull | | output | | |
| rden | | input | | |
| rdata | [DATAW-1:0] | output | | |
| empty | | output | | |
| data_cnt | [ ADDRW:0] | output | | |
要生成表格文件需要进行以下操作。首先sublime3 内执行python语句用的是内置的python3.3 ,并非系统中安装的python。内置的python3.3是没有办法用pip install给它安装第三方库。我选用openpyxl库来读写表格。python3.3的版本比较低,我首先升级到了sublime4,sublime4可以选择用python3.3和python3.8执行器,只需要在插件目录下新建一个.python-version的文件,里面写3.8或者3.3即可。再去下载python openpyxl库资源包,网址为:openpyxl · PyPI,下载时需要注意看该库支持的python版本,openpyxl 3.1.2 就支持python3.8。此外还需要下载et_xmlfile,网址为:et-xmlfile · PyPI,版本et-xmlfile 1.1.0即可。顺便说一下,Package Control - the Sublime Text package manager 可以下载sublime的插件,可以参考别人的插件是如何编写的,包括前面python-version文件的用法。下载完这个包后,解压出来拷贝包里的et_xmlfile和openpyxl文件夹到sublime的Packages目录下,注意不是外面那个带版本号et_xmlfile-1.1.0的文件夹。然后就可以开始写代码了。
第一步写类与前面类似,传入当前文件的路径。
file_name = self.view.file_name()
path,fname = os.path.split(file_name)
fname = fname.spilt(".")[0]
path和fname就是路径和文件不带后缀的名称
wb.save(path + "/" + fname + ".xlsx") #保存表格
写表格的代码如下:
import openpyxl import Workbook
def portXlsx_inst(mod_name,port_list,param_list,file_name):
name_lmax = 0
width_lmax = 0
prmonly_list = []
for _strl in param_list:
if _strl[0] == 'parameter':
width_lmax = max(len(_strl[1]),width_lmax)
name_lmax = max(len(_strl[2]),name_lmax)
prmonly_list.append(_strl)
for _strl in port_list:
width_lmax = max(len(_strl[1]),width_lmax)
name_lmax = max(len(_strl[2]),name_lmax)
wb = Workbook()
ws = wb.create_sheet("sheet1",0)
header = ["Name","Width","Dir","Description","other"]
for i,h in enumerate(header):
cell = ws.cell(1,i+1)
cell.value = h
for i,_strl in enumerate(prmonly_list):
# name width dir value
column_data = [_strl[2],_strl[1],"",_strl[3]]
for j,_strc in enumerate(column_data):
cell = ws.cell(i+2,j+1) #行,列
cell.value = _strlc
for i,_strl in enumerate(port_list):
# name width dir value
column_data = [_strl[2],_strl[1],_strl[0],""]
for j,_strc in enumerate(column_data):
cell = ws.cell(i+2+len(prmonly_list),j+1) #行,列
cell.value = _strlc
file_name = self.view.file_name()
path,fname = os.path.split(file_name)
fname = fname.spilt(".")[0]
wb.save(path + "/" + fname + ".xlsx") #保存表格
添加这2个功能后,文本的鼠标右键菜单一直有它们,并不是只有v和sv ,svh文件才显示,
1.修改了Verilog Gadget.py中引用的地方,在逗号后面加个空格,整体和前面一致,from .core.vgcore import (a, b, ...),每一个类名称前面要有一个空格,
2.sublime-settings中添加"Copy port to MarkDown": "show",
3.VerilogGadgetPortToMarkdown类中添加方法:
def is_visible(self):
return check_ext_cmd(self.view.file_name(), self.view.name(), 'Copy port to MarkDown')
修改这些地方后sublime插件区分文件类型就生效了。
12.编写了一个side bar的插件,需要在文件名后缀是 .f 时才生效。需要在Class后面继承的类引用WindowCommand。即
class CountcodeCommand(sublime_plugin.TextCommand,sublime_plugin.WindowCommand):
然后实现is_visible 和 is_enabled 函数,这样在右键文件时就会调用这2个函数来决定插件是否隐藏和是能。默认2个函数都是return True的。
但是会出现在同一个文件上右键第二个的时候,插件按钮始终是灰色的。把继承的sublime_plugin.TextCommand在class声明中删除就可以了,但这就意味着无法进行edit修改。暂时还不知道如何修改。
13.Lint
SublimeLinter说只能package control安装,离线下载SublimeLinter和SublimeLinter-contrib-iverilog-master.zip后,iverilog还是不能用。这是因为缺jsonschema依赖,在外网环境拷贝前面2个包到Packages目录下,再重启sublime,工具会自动下载这个依靠,拷贝到内网环境,还是提示不能import events,还需要拷贝Installed Packages/0_package_control_loader.sublime-package,这个文件中指示需要把jsonschema当初是依赖,而不是一个普通的python库。再修改一下Linter的配置就可以了。