sublime text3插件开发例程

github

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)intInserts 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的配置就可以了。