在之前的文章《ChatGPT 加持下的 Code Review 探究》中,介绍了咱们团队内运用了VS Code来进行code review。为什么开发这样一款插件,包老师的文章也介绍了下面两点原因
Reviewer 需求切换到 Web 浏览器中进行 Review,而 Web 浏览器中的代码展现风格、样式等等和自己的 IDE 中的偏好设置并不一样,这会影响 Reviewer 看代码的效率。
假如 Reviewer 觉得某段代码能够优化,但自己也没有掌握该如何优化,自己供给的代码也需求测试验证,所以他一般会需求这么操作:把代码拷贝到自己所运用的 IDE 中,优化完代码后,再粘贴到 Merge Request 中的谈论中,这个过程是比较繁琐的。不管如何,编码这个环节肯定是在自己最了解的 IDE 中进行才是最高效的,比如还能够运用 Github Copilot 插件对改变代码进行优化。
本节我将介绍这个插件的具体完成。
先来看插件的整体布局
需求完成的功用主要如下
- 账号登录
- 分类MR列表展现
- 点击左边MR中的改变文件能够在右侧editor区域展现对应文件更改比照、谈论操作
- 鄙人方区域中显现对应修正的ChatGPT剖析陈述
- reviewer收到review告诉后能够快速翻开vscode进行reviewer
信任不少同学运用过GitHub Pull Requests
和Gitlab workflow
插件,这两个插件都能够显现MR的改变,刚开始咱们也想用这个插件,经过调研,成果很遗憾,这两个插件都不适用于咱们团队
- 账号只能登录登录官方网站的账号,咱们公司的是内部私有的库房
- 分类展现也只能显现当时翻开项目的MR,咱们review搭档的代码不或许说需求翻开指定的项目吧,这样review也太麻烦了,不符合咱们提效的方针
- 无法做到定制内容,例如:显现区域内容,咱们需求显现ChatGPT剖析陈述(剖析陈述存放于另一个体系)、链接翻开mr改变、copilot剖析等
根据如上述求,那就咱们自己写一个符合团队需求的插件,下面我将介介绍插件的部分功用的完成。
或许部分同学没开发过vscode插件,这不要紧,假如你是前端同学,入门也适当的简单,引荐检查
插件根本装备
首先咱们要在activeBar
中声明一个运用容器ysfWorkbench
,然后给ysfWorkbench
中绑定一个view
(mergeRequest
)
package.json
...
+ "contributes": {
+ "viewsContainers": {
+ "activitybar": [
+ {
+ "id": "ysfWorkbench", // 这儿是容器id,后边绑定视图需求运用
+ "title": "Ysf Workbench",
+ "icon": "assets/images/ys.png"
+ }
+ ]
+ },
+"views": {
+ "ysfWorkbench": [ // 将view绑定到上面的简单中
+ {
+ "id": "mergeRequest", // 在后边view显现的内容会用到
+ "name": "Ysf Workbench",
+ "default": true
+ }
+ ],
+ }
...
登录
在拉取MR数据之前,咱们需求登录账号,vscode
交心的为咱们供给了未登录的占位内容,用于显现登录内容,只需求在package.json
中参加viewsWelcome
, 当vscode
context
中ysf.gitlab:noAccount
不存在时显现占位内容
package.json
...
contributes: {
+ "viewsWelcome": [
+ {
+ "view": "mergeRequest",
+ "contents": "欢迎运用vscode插件code review, 因为您还未登录gitlab账号,请先登录n [当即登录](command:ysf.gitlab:authenticate)",
+ "when": "ysf.gitlab:noAccount" // 读取`context`中的`ysf.gitlab:noAccount`值
+ }
+ ],
}
...
此刻,用户点击当即登录
按钮,即可履行ysf.gitlab:authenticate
指令,登录gitlab账号
vscode.commands.registerCommand('ysf.gitlab:authenticate', login)),
引发登录
登录采用gitlab OAuth进行认证,首先咱们需求申请一个Applications
,具体操作可可检查
在login
函数中拼接好url
,然后经过window.open翻开链接。格局大概如下
extension.ts
function login() {
const REDIRECT_URI = 'vscode://ysf.ysf-vsc-plugin/authentication'
const APP_ID = '803a04de757168037e1e6c87q3cb4b3ea881e4b52bde9fa1819b3b6d02c68b4d'
window.open(`https://gitlab.example.com/oauth/authorize?client_id=${APP_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&state=STATE&scope=${REQUESTED_SCOPES}&code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256`)
}
随后翻开浏览器跳转到gitlab
进行授权,授权完结后会自动翻开vscode
(就是浏览器会根据翻开链接REDIRECT_URI
),此刻gitlab
会拼接上授权code={xxx}
翻开vscode,格局大概如下
window.location.href='vscode://ysf.ysf-vsc-plugin/authentication?code=311f08d9c2f5eb7328502764c6a055ade9758c3432403e0a895694ea7a78e372'
vscode 验证登录
浏览器在翻开浏览器的时分,会带上途径/authentication
,跟咱们的路由是不是很像,那咱们怎样取拿到这个途径信息呢?vscode
也很交心的为咱们供给了registerUriHandler
这个办法进行监听
咱们只需求略微加点代码
// 完成一个UriHandle实例
class GitLabUriHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.UriHandler {
async handleUri(uri: vscode.Uri): Promise<void> {
this.fire(uri);
}
}
const gitlabUriHandler = new GitLabUriHandler();
// 注册uri监听
vscode.window.registerUriHandler(gitlabUriHandler);
// 注册监听回调,当翻开vscode时,会自履行gitlabUriHandler.handleUri()
gitlabUriHandler.event((uri) => {
const { path, query, code } = uri;
if (path === '/authentication') { // 这个途径说明是登录
// 在这儿进行登录验证,经过code,调用github登录接口
// 具体可检查https://docs.gitlab.com/ee/api/oauth2.html
// 请求api https://gitlab.example.com/oauth/token 验证登录
// .......此处省略888行代码.....
const accessToken = //.... 经过code换取的凭证
if (accessToken) {
// 设置插件上下文,表示已登录账号,这个时分 `mergeRequest`会显现成 treeView视图
// 履行setContext会改写插件视图,类似于react中的setState,显现不同的内容
vscode.commands.executeCommand('setContext', 'ysf.gitlab:noAccount', false);
}
}
})
MR分类列表
注册treeView
接下来咱们需求在extension.ts
中注册一个treeView
(什么是treeView),
extension.ts
// 注册树形视图
vscode.window.registerTreeDataProvider('mergeRequest', mergeRequestProvider);
在mergeRequestProvider
中只需求完成TreeDataProvider中的办法
class MergeRequestProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
// 主要完成下面两个办法
getChildren(element) { // 用户获取子元素
// 这儿编写获取子集的代码
// 这儿可参阅官方的例子
return [
// 例如 new ChangeFile(mr) // mr信息
]
}
getTreeItem(element) { // 用于获取树形视图中的元素的图标、文本和其他显现属性。这个办法负责返回树形视图中各个元素的显现信息
return element // 这儿可做更改
}
}
const mergeRequestProvider = new MergeRequestProvider():
class Item extends vscode.TreeItem {
constructor(label) {
super(label) // 还能够传入其他属性,tooltip,description等
}
iconPath = { //标题前的图标,
dark: // 深色图标途径
light: // 亮色图标途径
}
}
export default mergeRequestProvider; // 导出treeData实例
获取MR列表、MR文件改变列表
在上面的代码中,咱们注册了显现的treeData
的provider
,咱们能够在getChildren
中编写异步代码,比如咱们请求分支列表数据
和分支的改变文件数据
,下面是获取改变文件的例子
接口
// 假定现已在接口获取了MR的数据,咱们就从接口中拉取改变文件数据
class ChangeFile {
constructor(mr) {
this.mr = mr; // mr信息,包行 mr_iid, project_id
}
async getChildren(element) { // 获取files
const {changes} = fetchMrDetail(mr) // 经过mr_iid, project_id获取数据, https://docs.gitlab.com/ee/api/merge_requests.html#get-single-merge-request-changes
// diffs.map(diff => {
return FileItemModal(changes); // cha
})
....
}
changes 有如下属性
[{
"old_path": "VERSION",
"new_path": "VERSION",
"a_mode": "100644",
"b_mode": "100644",
"diff": "@@ -1 +1 @@ -1.9.7 +1.9.8",
"new_file": false,
"renamed_file": false,
"deleted_file": false
}]
点击文件显现改变
当点击最后一级文件时,咱们要翻开一个diff
的窗口,vscode供给了内置指令vscode.diff
,咱们在最后一级绑定一个command
,
vscode.diff
需求传入三个参数
1.老文件的uri
2.新文件的uri
3.title
代码如下
export class ChangedFileModal extends vscode.TreeItem {
constructor(options: { change, mr, mrVersion}) { // mrVersion
const { change, mr, mrVersion } = options;
super(vscode.Uri.file(file.new_path));
this.description = file.new_path;
const uris = getBaseAndHeadUri(mr, mrVersion, file); // 构建vscode需求的uri,这儿会参加query信息,包括mr_iid, project_id、commitId等信息,方便在后续用到
this.headFileUri = uris.headFileUri;
this.baseFileUri = uris.baseFileUri;
this.command = { // 绑定指令
title: 'Show changes',
command: VS_COMMANDS.DIFF, // 指令为`vscode.diff`
arguments: [
this.baseFileUri,
this.headFileUri,
`${path.basename(change.new_path)} (!${mr.iid})`,
],
};
}
}
注册文件体系
在你点击文件名想检查对饮的文调用文件比照指令时,此刻你会发现vscode报错了
这是因为vscode
默许是读取本地文件,然而咱们本地没有这些文件,vscode没有供给经过长途文件读取文件内容的接口,可是为咱们供给了自定义文件体系的provider:registerFileSystemProvider
// 注册文件review
vscode.workspace.registerFileSystemProvider(
REVIEW_URI_SCHEME, // 协议名称,自定义,文件体系标识
new ReviewFileSystem(),
ReviewFileSystem.OPTIONS, // 传入为非只读形式
);
// 官方供给的ReadOnlyFileSystem,需求完成readFile来获取文件内容、stat获取文件信息
class ReviewFileSystem extends ReadOnlyFileSystem {
static OPTIONS: RegisterOptions = {
isReadonly: false, // 是否只读,这个在咱们调用copilot有用
};
// 获取文件
// 假如是老改变文件这传入是vscode.diff的第一个参数,即arguments[2]
// 假如是新改变文件这传入是vscode.diff的第二个参数,即arguments[1]
async readFile(uri: vscode.Uri): Promise<Uint8Array> {
const params = fromReviewUri(uri); // 这儿会获取一些query信息,例如r_iid, project_id、commitId等
if (isEmptyFileUri(uri)) return new Uint8Array();
return this.#readFileRemote(params); // 获取长途文件内容,需求转换成Buffer Uint8Array
}
async stat(uri: vscode.Uri): Promise<vscode.FileStat> {
let size: number;
const params = fromReviewUri(uri);
if (isEmptyFileUri(uri)) {
size = 0;
} else {
size = await this.#sizeRemote(params); // 这儿也会调用this.#readFileRemote获取文件打巨细
}
return {
type: vscode.FileType.File,
ctime: Date.now(),
mtime: Date.now(),
size,
};
}
}
至此,文件内容现已能够显现在editor中,留意这儿会调用4次获取文件内容,有两次获取原改变的内容和巨细,有两次获取新改变的内容和巨细
经过popo快速翻开MR进行review
在开发上面的功用登录认证中咱们经过监听/authentication
来完结用户登录验证,给我一个考虑:咱们现已有popo
告诉,能不能经过popo
告诉来引发vscode
来进行review
,这样就不用来查找自己需求review的MR?
经过调研,尽管咱们无法经过popo
直接翻开vscode,可是咱们能够经过http链接翻开浏览器,运用浏览器这个中间人来翻开vscode
所以在告诉中加上了如下信息
vscode检查:https://gate.netease.com/workbench/api/workbench/public/vsc?projectId=110576&mrIid=161
访问这个链接后会用过js翻开vscode
<script>
window.location.href = 'vscode://ysf.ysf-vsc-plugin/mr?projectId=110576&mrIid=161';
</script>
显现popo进入的MR视图,然后在popoEnterMrProvider
中完成获取MR的信息,更分类获取MR信息处理一样
gitlabUriHandler.event((uri) => {
const { path, query, code } = uri;
if (path === '/authentication') { // 这个途径说明是登录
...
}
+ if (path === 'mr') {
+ // 这儿咱们在显现MR列表中再加一个view,当`path === 'mr'`时显现这个view,同时咱们在注册一个`treeDataProvider:popoEnterMrProvider`
+ // 存储地址栏的参数信息
+ // 履行setContext会改写插件视图,类似于react中的setState,显现不同的内容
+ // 同时能够做些优化:比如选中第一个文件
+ vscode.commands.executeCommand('setContext', 'ysf.mr.popo.enter', true);
+ }
})
在package.json中声明一个view,
...
views": {
"ysfWorkbench": [ // 将view绑定到上面的简单中
{
"id": "mergeRequest", // 在后边view显现的内容会用到
"name": "Ysf Workbench",
"default": true
},
+ {
+ "id": "popoEnterMR",
+ "name": "POPO链接进入的MR",
+ "default": true
+ "when": "ysf.mr.popo.enter && !ysf.gitlab:noAccount", // 当ysf.mr.popo.enter存在时会显现改view
+ }
],
}
...
效果如下
显现ChatGPT剖析陈述
因为咱们的web体系中现已存放了剖析陈述,所以在vscode只要能展现出来即可,这儿运用webview显现在editor
下方是个不错的选择
鄙人方面板中拓荒一个tab
出来用于显现咱们的陈述
"contributes": {
"viewsContainers": {
"activitybar": [
...
],
"panel": [
{
"id": "ysfReportPanel", // 这儿声明一个面板
"title": "ChatGPT剖析陈述",
"icon": "assets/images/ys.png"
}
]
},
}
在extension.ts
中注册webview
,显现咱们陈述信息,当文件切换时告诉webview页面改写陈述
vscode.window.registerWebviewViewProvider(ysfReportPanel, {
resolveWebviewView: (webviewView) => {
webviewView.webview.options = {...}; // 一些装备
webviewView.title = 'ChatGPT剖析陈述';
webviewView.webview.html = '....' // 这儿是咱们的html页面,一般是经过webpack打包后的html文件的内容
webviewView.webview.onDidReceiveMessage((message) => {
// 这儿主要是和webview页面进行通讯
// 例如当检查的文件变化时,告诉页面重新请求对应的陈述
// 注册当活动文件变化时的回调函数, 告诉webview文件发生变化
const editorDisposable = vscode.window.onDidChangeActiveTextEditor(async (editor) => {
if (editor) {
// 获取当时文件的 URI
const fileUri = editor.document.uri;
if (fileUri.scheme === REVIEW_URI_SCHEME) {
webviewView.webview.postMessage({
command: messageCommand.REPORT_FILE_CHANGED, // 告诉文件改变了,在webview页面中经过 window.addEventListener('message', callback)
data: fromReviewUri(fileUri),
});
}
}
});
})
}
})
效果如下图
copilot功用增强
运用copilot协助阅览代码
前文说到,咱们在体系默许的文件比照视图中是无法运用copilot右键功用的,这是因为在copilot源码中禁用了相关功用,当辨认到只读形式后是无法显现的
那咱们自定义的文件体系就排上用场了,只需求将isReadonly
传入false
,咱们的copilot就能够正常运用
vscode.workspace.registerFileSystemProvider(
REVIEW_URI_SCHEME, // 协议名称,自定义,文件体系标识
new ReviewFileSystem(),
{isReadonly : false}, // 传入为非只读形式
);
运用copilot协助优化代码
当咱们review一段代码的时分咱们觉得很有优化空间,期望借助copilot快速帮咱们想到优化方案,这个时分或许咱们会把这部分代码拷贝到chat窗口输入,这其实也是一个高频重复且繁琐的事,有没有方便的办法呢?
我经过检查插件的代码(copilot插件代码没开源,这儿能够直接找到插件的装置目录,然后直接阅览里边的代码,里边是写编译后的代码,可是不要紧,咱们只需求先格局化代码,仍是能看出些大概得)。在咱们点击explain this
在github copilot
的源码里是调用了一个指令github.copilot.interactiveEditor.explain
,那么咱们也能够在自己的插件里增加一个右键菜单。加一个命定来调用这个copilot
的指令,完成如下
vscode.commands.registerCommand(`ysf.copilot.ptimize`, () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const selection = editor.selection;
const text = editor.document.getText(selection);
// 获取当时文件的后缀名
const languageId = editor.document.languageId;
// 尽或许罗列下一切言语
const languageMap: Record<string, string> = {
js: 'js',
ts: 'ts',
... // 用于映射对应的言语,以便copilot辨认更精准
};
const language = languageMap[languageId] || 'js';
vscode.commands.executeCommand('github.copilot.interactiveEditor.explain', `@workspace 帮我优化下面这段代码 nn ``` ${language} n${text} n``` nn`);
})
这个插件还能够进行代码谈论,这部分功用假如有感兴趣的同学,我后边再专门关于如安在vscode谈论的文章,这儿我就不介绍了
以上是咱们在vscode review的一些实践,期望能对大家有所启发。