VSCode插件开发实战-实现go文件import排序

背景

最近在参与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.md

extension.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...

欢迎使用~

相关推荐
27669582921 天前
阿里1688 阿里滑块 231滑块 x5sec分析
java·python·go·验证码·1688·阿里滑块·231滑块
Moment2 天前
在 NodeJs 中如何通过子进程与 Golang 进行 IPC 通信 🙄🙄🙄
前端·后端·go
唐僧洗头爱飘柔95273 天前
(Go基础)变量与常量?字面量与变量的较量!
开发语言·后端·golang·go·go语言初上手
黑心萝卜三条杠3 天前
【Go语言】深入理解Go语言:并发、内存管理和垃圾回收
google·程序员·go
不喝水的鱼儿3 天前
【LuatOS】基于WebSocket的同步请求框架
网络·websocket·网络协议·go·luatos·lua5.4
微刻时光3 天前
程序员开发速查表
java·开发语言·python·docker·go·php·编程语言
lidenger4 天前
服务认证-来者何人
后端·go
幼儿园老大*4 天前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
童先生4 天前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
幼儿园老大*5 天前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go