在之前的文章《ChatGPT 加持下的 Code Review 探究》中,介绍了咱们团队内运用了VS Code来进行code review。为什么开发这样一款插件,包老师的文章也介绍了下面两点原因

Reviewer 需求切换到 Web 浏览器中进行 Review,而 Web 浏览器中的代码展现风格、样式等等和自己的 IDE 中的偏好设置并不一样,这会影响 Reviewer 看代码的效率。

假如 Reviewer 觉得某段代码能够优化,但自己也没有掌握该如何优化,自己供给的代码也需求测试验证,所以他一般会需求这么操作:把代码拷贝到自己所运用的 IDE 中,优化完代码后,再粘贴到 Merge Request 中的谈论中,这个过程是比较繁琐的。不管如何,编码这个环节肯定是在自己最了解的 IDE 中进行才是最高效的,比如还能够运用 Github Copilot 插件对改变代码进行优化。

本节我将介绍这个插件的具体完成。

先来看插件的整体布局

在VS Code中Code Review的实践

需求完成的功用主要如下

  • 账号登录
  • 分类MR列表展现
  • 点击左边MR中的改变文件能够在右侧editor区域展现对应文件更改比照、谈论操作
  • 鄙人方区域中显现对应修正的ChatGPT剖析陈述
  • reviewer收到review告诉后能够快速翻开vscode进行reviewer

信任不少同学运用过GitHub Pull RequestsGitlab workflow插件,这两个插件都能够显现MR的改变,刚开始咱们也想用这个插件,经过调研,成果很遗憾,这两个插件都不适用于咱们团队

  • 账号只能登录登录官方网站的账号,咱们公司的是内部私有的库房
  • 分类展现也只能显现当时翻开项目的MR,咱们review搭档的代码不或许说需求翻开指定的项目吧,这样review也太麻烦了,不符合咱们提效的方针
  • 无法做到定制内容,例如:显现区域内容,咱们需求显现ChatGPT剖析陈述(剖析陈述存放于另一个体系)、链接翻开mr改变、copilot剖析等

根据如上述求,那就咱们自己写一个符合团队需求的插件,下面我将介介绍插件的部分功用的完成。

或许部分同学没开发过vscode插件,这不要紧,假如你是前端同学,入门也适当的简单,引荐检查

插件根本装备

首先咱们要在activeBar中声明一个运用容器ysfWorkbench,然后给ysfWorkbench中绑定一个viewmergeRequest

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 contextysf.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文件改变列表

在上面的代码中,咱们注册了显现的treeDataprovider,咱们能够在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报错了

在VS Code中Code Review的实践

这是因为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次获取文件内容,有两次获取原改变的内容和巨细,有两次获取新改变的内容和巨细

在VS Code中Code Review的实践

经过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
        + }
     ],
  }
  ...

效果如下

在VS Code中Code Review的实践

显现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),
              });
            }
          }
        });
      })
  }
})

效果如下图

在VS Code中Code Review的实践

copilot功用增强

运用copilot协助阅览代码

前文说到,咱们在体系默许的文件比照视图中是无法运用copilot右键功用的,这是因为在copilot源码中禁用了相关功用,当辨认到只读形式后是无法显现的

在VS Code中Code Review的实践
那咱们自定义的文件体系就排上用场了,只需求将isReadonly 传入false,咱们的copilot就能够正常运用

  vscode.workspace.registerFileSystemProvider(
   REVIEW_URI_SCHEME, // 协议名称,自定义,文件体系标识
   new ReviewFileSystem(),
   {isReadonly : false}, // 传入为非只读形式
  );

运用copilot协助优化代码

当咱们review一段代码的时分咱们觉得很有优化空间,期望借助copilot快速帮咱们想到优化方案,这个时分或许咱们会把这部分代码拷贝到chat窗口输入,这其实也是一个高频重复且繁琐的事,有没有方便的办法呢?

在VS Code中Code Review的实践
我经过检查插件的代码(copilot插件代码没开源,这儿能够直接找到插件的装置目录,然后直接阅览里边的代码,里边是写编译后的代码,可是不要紧,咱们只需求先格局化代码,仍是能看出些大概得)。在咱们点击explain this
在VS Code中Code Review的实践
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的一些实践,期望能对大家有所启发。