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

欢迎使用~

相关推荐
Code季风1 小时前
微服务分布式配置中心:Gin Web 服务层与 gRPC 服务层集成 Nacos 实战
分布式·微服务·rpc·架构·go·gin·consul
考虑考虑3 小时前
go中的Map
后端·程序员·go
DemonAvenger6 小时前
Go中UDP编程:实战指南与使用场景
网络协议·架构·go
活椰拿铜6 小时前
Go实现超时控制
go
程序员爱钓鱼7 小时前
Go项目上线部署最佳实践:Docker容器化从入门到进阶
后端·google·go
Cyltcc1 天前
如何安装和使用 Claude Code 教程 - Windows 用户篇
人工智能·claude·visual studio code
Joker-01111 天前
牛客周赛Round 99(Go语言)
go·牛客周赛
UrbanJazzerati1 天前
使用 Thunder Client 调用 Salesforce API 的完整指南
面试·visual studio code
Code季风1 天前
Gin Web 层集成 Viper 配置文件和 Zap 日志文件指南(下)
前端·微服务·架构·go·gin
Code季风1 天前
Gin Web 服务集成 Consul:从服务注册到服务发现实践指南(下)
java·前端·微服务·架构·go·gin·consul