背景
最近在参与go项目开发时,发现团队其他小伙伴使用的是Goland,而我使用的是VSCode+gopls。gopls默认使用goimports格式化代码,且无法修改,这样子每次提交代码时import的部分会频繁变动,不利于项目的维护。趁着这个机会,学习了下vscode的插件开发,实现go文件import按字母表顺序排列。
环境
开发VSCode插件只需要安装好VSCode和Node.js,就可以进行开发了。
生成项目
首先安装 yeoman 脚手架生成工具
            
            
              cmd
              
              
            
          
          npm install -g yo 
npm install -g generator-code然后生成插件项目
            
            
              shell
              
              
            
          
          yo code此时会询问你插件的相关信息,我们使用JS开发,插件命名为 go-imports-alphabetical

最后直接用vscode打开 
生成的项目结构很简单,我们所有的逻辑写在 extension.js 即可。
            
            
              shell
              
              
            
          
          drwxr-xr-x 1 KZJ 197609    0 Feb 24 12:46 .git/
drwxr-xr-x 1 KZJ 197609    0 Feb 24 12:46 .vscode/
drwxr-xr-x 1 KZJ 197609    0 Feb 24 12:46 node_modules/
drwxr-xr-x 1 KZJ 197609    0 Feb 24 12:46 test/
-rw-r--r-- 1 KZJ 197609  429 Feb 24 12:46 .eslintrc.json
-rw-r--r-- 1 KZJ 197609   34 Feb 24 12:46 .gitignore
-rw-r--r-- 1 KZJ 197609  113 Feb 24 12:46 .vscode-test.mjs
-rw-r--r-- 1 KZJ 197609  144 Feb 24 12:46 .vscodeignore
-rw-r--r-- 1 KZJ 197609  250 Feb 24 12:46 CHANGELOG.md
-rw-r--r-- 1 KZJ 197609 2.0K Feb 24 12:46 README.md
-rw-r--r-- 1 KZJ 197609 1.4K Feb 24 12:46 extension.js
-rw-r--r-- 1 KZJ 197609  183 Feb 24 12:46 jsconfig.json
-rw-r--r-- 1 KZJ 197609  98K Feb 24 12:46 package-lock.json
-rw-r--r-- 1 KZJ 197609  738 Feb 24 12:46 package.json
-rw-r--r-- 1 KZJ 197609 2.6K Feb 24 12:46 vsc-extension-quickstart.mdextension.js

默认注册了一个helloworld命令,会弹出消息,我们直接按下f5会打开调试窗口。
在调试窗口按 CTRL+SHIFT+P,然后输入 Hello World就会调用该插件。

开发
了解了以上信息后我们就可以进行插件开发了。
分析需求
我们希望对go文件的import包进行排序,所以我们需要先解析出import的范围,然后把每一行的pakcage字符串放进js数组中,调用JsArray::sort(),最后修改原先的import范围的字符串就行。
准备工作
首先我们将extension.js中原来的命令由 go-imports-alphabetical.helloWorld 修改为 go-imports-alphabetical.sortImportsInAlphabetical,这个命令和package.json是要对应的,
我们也修改package.json中的相应属性。title即为Ctrl+Shift+p 时显示的命令。

获取当前文档
            
            
              js
              
              
            
          
          const editor = vscode.window.activeTextEditor
if (!editor) {
	error("no activated editor")
	return
}
const document = editor.document
let text = document.getText()直接使用vscode提供的api获取当前打开的代码文件。
然后调用document.getText()便可以获取达凯的文件的字符串。
获取import范围
使用正则来匹配import的范围,典型的go文件import格式为
            
            
              go
              
              
            
          
          import (
    "fmt"
    "os"
    
    "github.com/gin/"
)所以我们定义正则然后调用 JsString::match() 来匹配
            
            
              javascript
              
              
            
          
          const regex = new RegExp("(import\\s*\\()([\\s\\S]+?)(\\))")
const matchResult = text.match(regex)
let pendingPackages = matchResult[2].split(\n)*由于在正则使用了(),text.match()会返回匹配的字符串和括号中的内容,matchResult[2]就是上面go文件的
            
            
              arduino
              
              
            
          
              "fmt"
    "os"
    
    "github.com/gin/"这部分内容
然后我们再使用split即可获取每一行的package的字符串组成的数组。
最后我们遍历pendingPackages,去除空行,调用sort,便能获取排序后的数组,同时去除空行。
            
            
              js
              
              
            
          
          let packages = []
for (let i in pendingPackages) {
		const val = pendingPackages[i].trim()
		if (val.length == 0) continue
		packages.push(pendingPackages[i])
}
packages = packages.sort()之后便可以修改原先的import为排序后的import了。
修改代码
VScode提供了两个类表示编辑框中的光标和选中的范围。
vscode.Position 类
构造器 (line: number, character: number);
指代第几行第几个字符,类似我们写代码时候的光标位置。
如下图为 vscode.Position(1,3)
*注意和ide中显示的行、列不同,代码中是从0开始,ide的用户界面是从1开始

vscode.Range 类 构造器 (start: Position, end: Position);
指代代码范围,也就是我们选中某段代码的范围。
如下图为 vscode.Range( vscode.Position(1,1), vscode.Position(2,3) )
*注意和ide中显示的行、列不同,代码中是从0开始,ide的用户界面是从1开始 
了解了以上概念后便可以调用api修改代码了
            
            
              javascript
              
              
            
          
          let newImportCode = matchResult[1]
newImportCode += "\n"
for (let val of packages) {
		newImportCode += val
		newImportCode += "\n"
}
newImportCode += matchResult[3]
const offset = text.indexOf(matchResult[1])
const beginPos = document.positionAt(offset)
const endPos = document.positionAt(offset + matchResult[0].length)
const range = new vscode.Range(beginPos, endPos)
editor.edit((edit) => {
	edit.replace(range, newImportCode)
})使用 document.positionAt可以快速获取相对应的 vscode.Position,最后调用 editor.edit 来修改代码。
最后附上完整的代码 extension.js
            
            
              javascript
              
              
            
          
          const vscode = require('vscode');
function activate(context) {
	let disposable = vscode.commands.registerCommand('go-imports-alphabetical.sortImportsInAlphabetical', 
	sortImportsInAlphabetical);
	context.subscriptions.push(disposable);
}
function deactivate() { }
function info(...args) {
	vscode.window.showInformationMessage(...args)
}
function warn(...args) {
	vscode.window.showWarningMessage(...args)
}
function error(...args) {
	vscode.window.showErrorMessage(...args)
	console.error(...args)
}
function fatal(...args) {
	console.error(...args)
}
function debug(...args) {
	console.log(...args)
}
const regex = new RegExp("(import\\s*\\()([\\s\\S]+?)(\\))")
function sortImportsInAlphabetical() {
	try {
		const editor = vscode.window.activeTextEditor
		if (!editor) {
			error("no activated editor")
			return
		}
		const document = editor.document
		const text = document.getText()
		const matchResult = text.match(regex)
		if (!matchResult) {
			error("no go imports found")
			return
		}
		const pendingPackages = matchResult[2].split("\n")
		let packages = []
		for (let i in pendingPackages) {
			const val = pendingPackages[i].trim()
			if (val.length == 0) continue
			packages.push(pendingPackages[i])
		}
		packages = packages.sort()
		let newImportCode = matchResult[1]
		newImportCode += "\n"
		for (let val of packages) {
			newImportCode += val
			newImportCode += "\n"
		}
		newImportCode += matchResult[3]
		const offset = text.indexOf(matchResult[1])
		const beginPos = document.positionAt(offset)
		const endPos = document.positionAt(offset + matchResult[0].length)
		const range = new vscode.Range(beginPos, endPos)
		editor.edit((edit) => {
			edit.replace(range, newImportCode)
		})
	} catch (e) {
		fatal(e)
	}
}
module.exports = {
	activate,
	deactivate
}按下f5开始调试,在调试的窗口中打开一份go代码,使用快捷键CTRL+SHIFT+P,调用Sort Go Imports in Alphabetical就能发现go的import已经按我们想要的顺序排列了。



附录
该项目也进行了开源,进行了一些更新并持续维护中,欢迎提交需求,给个star。 github: github.com/AlpsMonaco/...
同时也已经在vscode marketplace上架
marketplace.visualstudio.com/items?itemN...

欢迎使用~