前端工程师新超能力-揭秘vscode插件开发

上篇文章说,采用html、css、js就能开发谷歌浏览器插件,打破前端职业瓶颈,拓宽开发思路。那么这次依然是使用前端常用的js、html来开发vscode插件。

vscode是前端开发中重要的IDE工具,自行安装好多功能插件,让日常开发写代码速度突飞猛进,再加上现在流行的AI工具,根据提示大大节省开发时间。这就是插件的魅力。

接下来开始研究研究vscode插件的开发,只使用前端日常使用的基础html、css、js就可以完成。是不是又打破了前端职业界限,给你的简历又添加新的亮点。

开发环境初始化

要开发VSCode插件,您可以按照以下步骤进行:
1:安装Node.js:确保计算机上安装Node环境。
2:安装Yeoman和VS Code Extension Generator:使用Node.js的包管理器npm全局安装Yeoman和VS Code Extension Generator。在命令行中运行以下命令来安装:

npm install -g yo generator-code

生成VS Code插件项目:在命令行中运行以下命令来生成一个新的VS Code插件项目:

yo code

按照提示输入您的插件项目的名称等信息。

  1. 开发插件:在生成的插件项目中,您可以编辑代码来实现您的插件功能。VS Code插件可以使用JavaScript或TypeScript编写。
  2. 调试插件:您可以使用VS Code自带的调试功能来调试您的插件。在插件项目中,您可以设置调试配置并启动调试会话。
  3. 发布插件:完成插件开发后,您可以将插件发布到VS Code的插件市场,让其他用户安装和使用您的插件。

基础插件helloWorld的实现

功能实现

接下来我们是创建项目的命令,进行初始化项目
前端工程师新超能力-揭秘vscode插件开发

yo code
     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension`---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `
   
? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? hello-world  //项目名称
? What's the identifier of your extension? hello-world
? What's the description of your extension? this is my first vscode plugin //项目描述
? Initialize a git repository? No //是否初始化git仓库,这个可以以后在package.json中进行设置
? Bundle the source code with webpack? Yes //是否使用vscode开发
? Which package manager to use? npm //包管理工具

前端工程师新超能力-揭秘vscode插件开发
初始化完成后,项目目录结构如下:
前端工程师新超能力-揭秘vscode插件开发
通过查看package.json,可以看到启动命令为watch
前端工程师新超能力-揭秘vscode插件开发
启动项目后,进行测试
前端工程师新超能力-揭秘vscode插件开发
接下来会打开一个新的窗口页面,用于调试
前端工程师新超能力-揭秘vscode插件开发
在新的窗口中,使用commond+shift+p打开调用插件命令,在输入框中输入helloWorld
前端工程师新超能力-揭秘vscode插件开发
选择命令后,可以看到右下角出现弹窗
前端工程师新超能力-揭秘vscode插件开发
说明我们实现的第一个插件hello world已经完成。

代码分析

package.json中可以看出代码入口文件,”main”: “./dist/extension.js”, dist目录是通过webpack工具进行生成。
接下来查看webpack.config.js配置文件,可以看出代码的入口文件'./src/extension.ts',从而可以知道插件的入口文件./src/extension.ts

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
	let disposable = vscode.commands.registerCommand('hello-world.helloWorld', () => {
		vscode.window.showInformationMessage('Hello World from hello-world!');
	});
	context.subscriptions.push(disposable);
}

export function deactivate() {}

除掉注释和空格,不到10行代码。
该文件导出2个方法,activatedeactivate;这是vscode官方的激活插件和销毁插件的方法。
接下来主要使用activate方法实现插件功能。

package.json中代码的配置

想要实现通过命令启动插件,还需要在package中配置

"contributes": {
  "commands": [
    {
      "command": "hello-world.helloWorld",
      "title": "Hello World"
    }
  ]
},

contributes是用来扩展vscode方法的配置,在示例中,注册了commands,也就是说配置了一个命令,hello-world.helloWorld,title则是这个命令在vscode正式环境中使用的名称,也就是用户使用时的名称。除了commands外,vscode还支持注册menus,keybindings,languages等多个配置,更多配置可以点击Contributes Point来查看。

改造command方法中的内容

创造出html的页面结构,将内容写入项目的index.html文件中

前端工程师新超能力-揭秘vscode插件开发

import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';

export function activate(context: vscode.ExtensionContext) {
  let disposable = vscode.commands.registerCommand(
    'hello-world.helloWorld',
    () => {
      const content = `<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
	<h1>Hello World</h1>
	<div>vscode plugin create html page</div>

</body>
</html>
`;
      // 获取项目路径地址
      const uri = vscode.workspace!.workspaceFolders![0].uri.fsPath;
      console.log(uri);
      fs.writeFile(path.join(uri, 'index.html'), content, (err) => {
        if (err) {
          vscode.window.showErrorMessage('创建失败');
        }
        vscode.window.showInformationMessage('创建成功');
      });
    }
  );
  context.subscriptions.push(disposable);
}

export function deactivate() {}

执行编译代码,在调试窗口中通过命令查看插件。执行hello world命令,可以看到在项目中生成新的页面。
配合一起脚本命令,生成动态数据,就可以放入html文件中进行展示,比如当前时间,git当前用户,git的提交记录,代码量分析等。

webview展示

除了上面生成html页面,vscode还支持生成webview页面。
使用vscode.window.createWebviewPanel 创建一些简单的页面内容, 效果类似于欢迎页, vscdoe的版本发行说明, 如下
前端工程师新超能力-揭秘vscode插件开发
代码:

import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';

export function activate(context: vscode.ExtensionContext) {
  let disposable = vscode.commands.registerCommand(
    'hello-world.helloWorld',
    () => {
      // 创建一个 weilcome
      const panel = vscode.window.createWebviewPanel(
        'welcome', // webview面板的类型, 内部使用
        '自定义欢迎页面标题', // table 标题
        vscode.ViewColumn.One, // 显示在第一个编辑器
        {
          enableScripts: true, // 控制脚本是否在webview内容中启用, 默认为false(脚本禁用)
          retainContextWhenHidden: true, // webview被隐藏时保持状态,避免被重置
        }
      );
	  panel.webview.html = `<h1>hello world</h1><div>welcome to vscode!</div>`;
    }
  );
  context.subscriptions.push(disposable);
}

export function deactivate() {}

重新编译,查看插件内容。
前端工程师新超能力-揭秘vscode插件开发

webView动态显示内容

同时配置git命令,可以查看当前项目的分支。
通过使用node的子进程,可以使用spawn命令执行git命令。

新建gitTools类。

前端工程师新超能力-揭秘vscode插件开发
在gitTools中实现runGitCommand方法,内部通过spawn来实现。

class GitTools {
    private cwd: string;
    private user: string;
  
  constructor(cwd: string) {
    this.cwd = cwd;
    this.user = '';
  }
  // 只简单实现一个git branch的命令,做测试使用
  async branch(){
  	const res = await this.runGitCommand('git branch');
  	return res;
  }
  runGitCommand(command:string) {
    return new Promise((resolve, reject) => {
      var process = spawn(command, {
        cwd: this.cwd,
        shell: true,
      });

      var logMessage = `${command}`;
      var cmdMessage = '';

      process.stdout.on('data', (data) => {
        console.log(`${logMessage} start ---`, data);
        if (!data) {
          reject(`${logMessage} error1 : ${data}`);
        } else {
          cmdMessage = data.toString();
        }
      });

      process.on('close', (data) => {
        console.log(`${logMessage} close ---`, data);
        if (data) {
          reject(`${logMessage} error2 ! ${data}`);
        } else {
          console.log(`${logMessage} success !`);
          resolve(cmdMessage);
        }
      });
    });
  }
}

前端工程师新超能力-揭秘vscode插件开发
完整的extension.ts代码

import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { spawn } from 'child_process';

export function activate(context: vscode.ExtensionContext) {
  let disposable = vscode.commands.registerCommand(
    'hello-world.helloWorld',
    () => {
      // 创建一个 weilcome
      const panel = vscode.window.createWebviewPanel(
        'gitBranch', // webview面板的类型, 内部使用
        '查看项目分支', // table 标题
        vscode.ViewColumn.One, // 显示在第一个编辑器
        {
          enableScripts: true, // 控制脚本是否在webview内容中启用, 默认为false(脚本禁用)
          retainContextWhenHidden: true, // webview被隐藏时保持状态,避免被重置
        }
      );
	  const git = new GitTools(vscode.workspace!.workspaceFolders![0].uri.fsPath);
	  git.branch().then((res)=>{
		console.log(res);
	  	panel.webview.html = `<h1>hello world</h1>
		<div>welcome to vscode! </div>
		<p>${res}</p>`;
	  });
    }
  );
  context.subscriptions.push(disposable);
}

class GitTools {
    private cwd: string;
    private user: string;
  constructor(cwd: string) {
    this.cwd = cwd;
    this.user = '';
  }
  async branch(){
	const res = await this.runGitCommand('git branch');
	return res;
  }
  runGitCommand(command:string) {
    return new Promise((resolve, reject) => {
      var process = spawn(command, {
        cwd: this.cwd,
        shell: true,
      });

      var logMessage = `${command}`;
      var cmdMessage = '';

      process.stdout.on('data', (data) => {
        console.log(`${logMessage} start ---`, data);
        if (!data) {
          reject(`${logMessage} error1 : ${data}`);
        } else {
          cmdMessage = data.toString();
        }
      });

      process.on('close', (data) => {
        console.log(`${logMessage} close ---`, data);
        if (data) {
          reject(`${logMessage} error2 ! ${data}`);
        } else {
          console.log(`${logMessage} success !`);
          resolve(cmdMessage);
        }
      });
    });
  }
}

export function deactivate() {}

查看调试窗口,可以看到本地的分支。
前端工程师新超能力-揭秘vscode插件开发

sideBar展示treeView

上面是在窗口中展示webView功能,那么如何在侧边拦显示相关数据呢?比如下面的源码管理
前端工程师新超能力-揭秘vscode插件开发
要实现这一功能,主要利用[vscode.window.createTreeView](https://code.visualstudio.com/api/references/vscode-api#window)这个API。需要2个参数
前端工程师新超能力-揭秘vscode插件开发

  • viewId:这个id是在package.json中进行自定义设置
  • options:提供的treeData数据

前端工程师新超能力-揭秘vscode插件开发

新建TreeProvider

class TreeProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
  // 创建一个事件发射器,用于通知树数据发生变化
  private _onDidChangeTreeData: vscode.EventEmitter<
    vscode.TreeItem | undefined
  > = new vscode.EventEmitter<vscode.TreeItem | undefined>();
  // 定义一个只读的事件,允许外部订阅树数据变化事件
  readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | undefined> =
    this._onDidChangeTreeData.event;

  // 定义刷新方法,用于通知视图数据发生变化
  refresh(): void {
    this._onDidChangeTreeData.fire(undefined);
  }

  // 获取树中的单个项目,这里可以定义如何显示单个项目
  getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
    return element;
  }

  // 获取树的子元素,可以是一个异步操作
  getChildren(element?: vscode.TreeItem): Promise<vscode.TreeItem[]> {
    return new Promise(async (resolve, reject) => {
      const gitTool = new GitTools(
        vscode.workspace.workspaceFolders![0].uri.fsPath
      );
      const userList: string = (await gitTool.allUser()) as string;
      const result = userList
        .split('\n')
        .filter(Boolean)
        .map((item: string) => {
          return new vscode.TreeItem(
            item,
            vscode.TreeItemCollapsibleState.None
          );
        });
      resolve(result);
    });
  }
}

使用到了gitTool的allUser方法,在GitTool中添加下allUser方法

async allUser() {
  // git log --pretty=format:"%an <%ae>"| sort -u
  try {
    const res = await this.runGitCommand(
      `git log --pretty=format:"%an <%ae>"| sort -u`
    );
    return res;
  } catch (err) {
    console.error(err);
  }
  return false;
}

设置package.json

package.json中添加”contributes”值:

  • “viewsContainers”
    • “activitybar”:
      • “id”:这个id值是下面views对象中的key
      • “title”:显示的标题
      • “icon”:用于侧边展示的图标ICON
  • “views”
    • “user-list”:这个key值是在activitybar中定义的id
      • “id”:这个id是创建treeview的标志
      • “name”:创建treeView的名称

这里需要特别注意:

  1. 定义views的key,一定要和activitybar的id对应;
  2. views下key中的id,是创建treeView的标志,一定要对应上。

前端工程师新超能力-揭秘vscode插件开发

"contributes": {
  "viewsContainers": {
    "activitybar": [
      {
        "id": "hello-list",
        "title": "helloWorld",
        "icon": "images/search.svg"
      }
    ]
  },
  "views": {
    "hello-list": [
      {
        "id": "git-userlist",
        "name": "项目成员"
      }
    ]
  },
}

重新编译项目,并运行启动调试
可以在新窗口中看到如下
前端工程师新超能力-揭秘vscode插件开发
sideBar的标题是有:activitybar.title+views.name 组合而成。

对treeView中数据进行事件操作

获取到了tree数据,就需要对每一个item的数据进行操作处理。常见的就是在item后添加处理事件。
可以通过在package.json中添加”menus”配置

  • “view/title”: 表示在sideBar的顶部添加按钮
  • “view/item/context”: 表示treeData数据的item每一项后添加事件按钮
"menus": {
  "view/title": [
    {
      "when": "view == git-userlist",
      "command": "userList.add",
      "group": "navigation"
    }
  ],
  "view/item/context": [
    {
      "when": "view == git-userlist",
      "command": "userList.item.check",
      "group": "inline"
    }
  ]
},
"commands": [
  {
    "command": "hello-world.helloWorld",
    "title": "Hello World"
  },
  {
    "command": "userList.item.check",
    "title": "查看"
  }
]

前端工程师新超能力-揭秘vscode插件开发
同时要在commands中进行对应配置
前端工程师新超能力-揭秘vscode插件开发
重新编译调试查看项目
前端工程师新超能力-揭秘vscode插件开发
然后在项目中添加对应的点击事件
context.subscriptions中添加事件处理
前端工程师新超能力-揭秘vscode插件开发

export function activate(context: vscode.ExtensionContext) {
  // 创建treeView
  vscode.window.createTreeView('git-userlist', {
    treeDataProvider: new TreeProvider(),
  });
  let disposable = vscode.commands.registerCommand(
    'hello-world.helloWorld',
    () => {
      // 创建一个 welcome
      const panel = vscode.window.createWebviewPanel(
        'gitBranch', // webview面板的类型, 内部使用
        '查看项目分支', // table 标题
        vscode.ViewColumn.One, // 显示在第一个编辑器
        {
          enableScripts: true, // 控制脚本是否在webview内容中启用, 默认为false(脚本禁用)
          retainContextWhenHidden: true, // webview被隐藏时保持状态,避免被重置
        }
      );
      const git = new GitTools(
        vscode.workspace!.workspaceFolders![0].uri.fsPath
      );
      git.branch().then((res) => {
        console.log(res);
        panel.webview.html = `<h1>hello world</h1>
		<div>welcome to vscode! </div>
		<p>${res}</p>`;
      });
    }
  );
  context.subscriptions.push(disposable,
    // 为了简便,直接添加事件注册
	  vscode.commands.registerCommand(`helloList.add`, () => {
      console.log(vscode.workspace!.workspaceFolders![0].uri.fsPath, 'add to git');
    }),
    vscode.commands.registerCommand(`helloList.item.check`, (user) => {
      let userName = user.label.split(' ')[0];
      console.log(user, 'checkcheckcheckcheck');
      vscode.window.showInformationMessage(`Hello ${userName}`);
    })
  );
}

前端工程师新超能力-揭秘vscode插件开发

设置Options选择框

有时候为了方便用户的输入,可以做出快速选择的内容提示框。如日期的选择,有本月,上个月,半年等。

使用的是vscode.window.showQuickPickAPI

import * as vscode from "vscode";

export function activate(context: vscode.ExtensionContext) {
    // 注册一个命令 learn-vscode-extends.quickPick
    const disposable = vscode.commands.registerCommand("learn-vscode-extends.quickPick", () => {
        // 打开一个快速选择列表
        vscode.window.showQuickPick(
            // ["选项一", "选项二", "选项三"], // 简单的显示多个选项
            [
                { // 对象的形式可以配置更多东西
                    label: "选项一",
                    description: "选项一描述$(bug)", // 可以指定官方提供的图标id https://code.visualstudio.com/api/references/icons-in-labels#icon-listing
                    detail: "选项一详细信息",
                    mate: { // 这里也可以放一些自定义的对象
                        script: "learn-vscode-extends.helloWrold" 
                    },
                },
                {
                    label: "选项二",
                    description: "选项二描述",
                    detail: "选项二详细信息$(gear)",
                }
            ],
            {
                title: "请选择一个选项", // 标题
                placeHolder: "用户类型", // 占位符文本
                canPickMany: false, // 是否可以多选
            }
        ).then((res: vscode.QuickPickItem | undefined) => {
            if (!res) return;
            console.log(res); // 这里就是上面数组中对应的对象信息
        })
    });

    context.subscriptions.push(disposable);
}

export function deactivate() { }

实战案例开发插件gitCodeStatics

gitCodeStatics插件使用

方便日常开发中统计代码提交量,以前使用git log再加上shell的统计计算,那么是不是就可以将这些功能做成一个插件更加方便使用呢,插件显示出所有的作者,选择作者后可以选择需要统计的日期。

前端工程师新超能力-揭秘vscode插件开发
前端工程师新超能力-揭秘vscode插件开发
以上代码统计记录来自 github.com/microsoft/v…, 微软官方的vscode插件示例项目。

插件已经发布,可以自行下载查看使用。marketplace.visualstudio.com/items?itemN…

开发流程

使用webpack和ts创建开发模板

参考上边helloWorld项目创建流程

创建gitTools类

统计项目下用户

这里使用node的 spawnSync 获取git log数据。

import { spawn, spawnSync } from 'child_process';
import dayjs = require('dayjs');
export class GitTools {
private cwd: string;
private user: string;
constructor(cwd: string) {
this.cwd = cwd;
this.user = '';
this.init();
}
async init() {
this.startChildProcess('git', ['remote', '-v'])
.then(async (res) => {
this.user = await this.startChildProcess('git', [
'config',
'user.name',
]);
console.log(this.user, 'username');
})
.catch((err) => {
console.error('git no remote', err);
});
}
async remote() {
try {
var params = ['remote', '-v'];
let result = await this.startChildProcess('git', params);
return result;
} catch (err) {
console.error(err);
}
}
async logMonth(params: any) {
// const res = await this.startChildProcess('echo', ['-e', 'A line1\nB line 2', '|', 'awk', 'BEGIN{ print "Start" } { print } END{ print "End" }']);
// 根据日期,作者,统计代码的提交行数
const res: unknown = await this.startChildProcessNoParams(
`git log --author="${params.author}" --pretty=tformat: --numstat --since=${params.since} --until=${params.until}`
);
let resArr: any[] = [];
(res as string).split('\n').forEach((item: any) => {
item && resArr.push(item.split('\t'));
});
if (resArr.length === 0) {
return 'No submitted data!';
}
// 处理添加行数据
let addLineNum = resArr.reduce((pre, current) => {
return (
(isNaN(parseInt(pre)) ? 0 : parseInt(pre)) +
(isNaN(parseInt(current[0])) ? 0 : parseInt(current[0]))
);
}, 0);
// 处理删除行数据
let delLineNum = resArr.reduce((pre, current) => {
return (
(isNaN(parseInt(pre)) ? 0 : parseInt(pre)) +
(isNaN(parseInt(current[1])) ? 0 : parseInt(current[1]))
);
}, 0);
return `add line: ${addLineNum} remove line: ${delLineNum} total edit line: ${
addLineNum + delLineNum
}`;
}
// 跨越多个月份
async logAcrossMonths(userName: string, since: string, until: string) {
let resultArr: Array<{ date: string; value: number | null }> = [];
let monthArr: any[] = [];
monthArr.push({
label: dayjs(since).format('YYYY-MM'),
start: since,
end: dayjs(since).endOf('month').format('YYYY-MM-DD'),
});
let diff = dayjs(until).diff(since, 'month');
for (let i = 1; i <= diff; i++) {
monthArr.push({
label: dayjs(since).add(i, 'month').format('YYYY-MM'),
start: dayjs(since)
.add(i, 'month')
.startOf('month')
.format('YYYY-MM-DD'),
end: dayjs(since).add(i, 'month').endOf('month').format('YYYY-MM-DD'),
});
}
if (
monthArr[monthArr.length - 1].label !== dayjs(until).format('YYYY-MM')
) {
monthArr.push({
label: dayjs(until).format('YYYY-MM'),
start: dayjs(until).startOf('month').format('YYYY-MM-DD'),
end: until,
});
}
/* await Promise.all(fileNames.map(async (file) => {
const contents = await fs.readFile(file, 'utf8');
console.log(contents);
})); */
const promiseAll = await Promise.all(
monthArr.map(async (item) => {
let res: any = await this.startChildProcessNoParams(
`git log --author="${userName}" --pretty=tformat: --numstat --since=${item.start} --until=${item.end}`
);
if (res.length === '') {
resultArr.push({
date: item.label,
value: null,
});
}
let resArr: any[] = [];
(res as string).split('\n').forEach((item: any) => {
item && resArr.push(item.split('\t'));
});
// 处理添加行数据
let addLineNum = resArr.reduce((pre, current) => {
return (
(isNaN(parseInt(pre)) ? 0 : parseInt(pre)) +
(isNaN(parseInt(current[0])) ? 0 : parseInt(current[0]))
);
}, 0);
// 处理删除行数据
let delLineNum = resArr.reduce((pre, current) => {
return (
(isNaN(parseInt(pre)) ? 0 : parseInt(pre)) +
(isNaN(parseInt(current[1])) ? 0 : parseInt(current[1]))
);
}, 0);
resultArr.push({
date: item.label,
value: addLineNum + delLineNum,
});
return resultArr;
})
);
// 查询代码量
// let returnVal = resultArr.filter((item) => Boolean(item.value));
// resolve(returnVal)
return promiseAll[0].filter((item) => Boolean(item.value));
}
async branch() {
try {
var params = ['branch', '-a'];
let result = await this.startChildProcess('git', params);
return result.toString();
} catch (err) {
console.error(err);
}
return false;
}
async allUser() {
// git log --pretty=format:"%an <%ae>"| sort -u
try {
const res = await this.startChildProcessNoParams(
`git log --pretty=format:"%an <%ae>"`
);
let resArr = (res as string).split('\n');
let resSet = new Set();
resArr.forEach((item) => {
item && resSet.add(item);
});
return [...resSet]
.sort((a: unknown, b: unknown) => {
return (a as string)[0] > (b as string)[0] ? 1 : -1;
})
.join('\n');
} catch (err) {
console.error(err);
}
return false;
}
async status() {
try {
var params = ['status', '-s'];
let result = await this.startChildProcess('git', params);
return result;
} catch (err) {
console.error(err);
}
return false;
}
startChildProcess(command: string, params: string[]): Promise<string> {
return new Promise((resolve, reject) => {
var process = spawn(command, params, {
cwd: this.cwd,
shell: true,
});
var logMessage = `${command} ${params[0]}`;
var cmdMessage = '';
process.stdout.on('data', (data) => {
console.log(`${logMessage} start ---`, data);
if (!data) {
reject(`${logMessage} error1 : ${data}`);
} else {
cmdMessage = data.toString();
}
});
process.on('close', (data) => {
console.log(`${logMessage} close ---`, data);
if (data) {
reject(`${logMessage} error2 ! ${data}`);
} else {
console.log(`${logMessage} success !`);
resolve(cmdMessage);
}
});
});
}
startChildProcessNoParams(command: string) {
return new Promise((resolve, reject) => {
var process = spawnSync(command, {
cwd: this.cwd,
shell: true,
encoding: 'utf8',
});
var logMessage = `${command}`;
var cmdMessage = '';
if (process.error) {
console.log('ERROR: ', process.error);
reject(process.error);
}
resolve(process.stdout);
});
}
}

进行user用户统计时,开始使用代码155行的shell命令进行,还有行数的统计使用了shell的awk进行统计。这样开发完成在Mac上可以正常使用,但是到了widonws系统就报错,windows的默认执行脚本工具是powershell,无法解析shell命令。
然后采用纯js进行数据解析处理。将log数据全部读取再进行相加。坑是一个一个的来,接下来遇到编辑器默认现在的log条数有限制,不能一次显示完,那这样就又无法统计。

只能采用node的 spawnSync APi强制把数据读取完全。所以新增了startChildProcessNoParams方法;才算是把踩的坑给填上,这样就能兼容windows系统了。

统计用户提交代码行数

默认实现功能:

  • 统计用户当月代码
  • 统计用户上个月的代码
  • 统计用户最近6个月的代码,可以作为图表显示
  • 自定义需要的统计日期

以上只是根据传入的查询日期不同,进行的分类统计
前端工程师新超能力-揭秘vscode插件开发

创建treeData类

将用户数据做成tree展示

主要用来显示项目的用户,可以放到vscode的左侧栏中,将用户数据做成tree展示。
前端工程师新超能力-揭秘vscode插件开发

查询数据来自 github.com/microsoft/v…,微软提供的vscode官方插件案例项目。

  async allUser() {
// 使用| sort -u shell命令进行统计,不兼容windows
// git log --pretty=format:"%an <%ae>"| sort -u
try {
const res = await this.startChildProcessNoParams(
`git log --pretty=format:"%an <%ae>"`
);
let resArr = (res as string).split('\n');
let resSet = new Set();
resArr.forEach((item) => {
item && resSet.add(item);
});
return [...resSet]
.sort((a: unknown, b: unknown) => {
return (a as string)[0] > (b as string)[0] ? 1 : -1;
})
.join('\n');
} catch (err) {
console.error(err);
}
return false;
}

查询出数据,将数据放入到TreeData.ts类中;

import * as vscode from 'vscode';
import { GitTools } from './gitTools';
// 创建一个类 TreeProvider,实现了 TreeDataProvider 接口
export default class TreeProvider
implements vscode.TreeDataProvider<vscode.TreeItem>
{
// 创建一个事件发射器,用于通知树数据发生变化
private _onDidChangeTreeData: vscode.EventEmitter<
vscode.TreeItem | undefined
> = new vscode.EventEmitter<vscode.TreeItem | undefined>();
// 定义一个只读的事件,允许外部订阅树数据变化事件
readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | undefined> =
this._onDidChangeTreeData.event;
// 定义刷新方法,用于通知视图数据发生变化
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
// 获取树中的单个项目,这里可以定义如何显示单个项目
getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
return element;
}
// 获取树的子元素,可以是一个异步操作
getChildren(element?: vscode.TreeItem): Promise<vscode.TreeItem[]> {
// 在这里实现获取树的子元素的逻辑
// 可以返回一个 Promise 来异步获取子元素
// 如果没有子元素,可以返回一个空数组
// 在实际使用中,你需要根据你的插件逻辑来实现这个方法
return new Promise(async (resolve, reject) => {
const gitTool = new GitTools(vscode.workspace.workspaceFolders![0].uri.fsPath);
const userList: string = await gitTool.allUser() as string;
const result = userList.split("\n").filter(Boolean).map((item: string) => {
return new vscode.TreeItem(item, vscode.TreeItemCollapsibleState.None)
});
resolve(result)
});
}
}

添加查看、刷新命令

前端工程师新超能力-揭秘vscode插件开发

修改package.json中添加配置
  • 在package.json中添加配置,contributes下新增viewsContainersviews

前端工程师新超能力-揭秘vscode插件开发
views的key是activitybar的id值,要相对应。

  • 给menus添加属性配置

前端工程师新超能力-揭秘vscode插件开发

  • 在commands中添加2个对应的命令

前端工程师新超能力-揭秘vscode插件开发
这里定义的命令,稍后在extension.js中使用

修改extension.js的代码

前端工程师新超能力-揭秘vscode插件开发

context.subscriptions.push(
disposable,
commands.registerCommand(`userList.refresh`, () => {
// console.log(workspace!.workspaceFolders![0].uri.fsPath, 'refresh to git');
// 刷新数据列表
window.createTreeView('gitcode-userlist', {
treeDataProvider: new TreeProvider(),
});
}),
commands.registerCommand(`userList.item.search`, (user) => {
let userName = user.label.split(' ')[0];
// 打开一个快速选择列表
window
.showQuickPick(
[
{
label: 'Current month',
detail: 'Submit code statistics current month',
},
{
label: 'Last month',
detail: 'Submit code statistics Last month',
},
{
label: 'Past six months',
detail: 'Submit code statistics Last six month',
},
{
label: 'Custom date query',
detail: 'setting custom query date ',
},
],
{
title: 'Query date', // 标题
placeHolder: 'Please select an option!', // 占位符文本
canPickMany: false, // 是否可以多选
}
)
.then((res: QuickPickItem | undefined) => {
if (!res) return;
const { label } = res;
// console.log(res, userName, dayjs().format(), 'userNameuserName'); // 这里就是上面数组中对应的对象信息
if (label === 'Current month') {
searchBySetDate(
userName,
dayjs().startOf('month').format('YYYY-MM-DD'),
dayjs().endOf('month').format('YYYY-MM-DD')
);
} else if (label === 'Last month') {
searchBySetDate(
userName,
dayjs().add(-1, 'month').startOf('month').format('YYYY-MM-DD'),
dayjs().add(-1, 'month').endOf('month').format('YYYY-MM-DD')
);
} else if (label === 'Past six months') {
searchByCustomDate(
userName,
dayjs().add(-5, 'month').startOf('month').format('YYYY-MM-DD'),
dayjs().endOf('month').format('YYYY-MM-DD')
);
} else {
// 自定义日期
searchByCustomDate(userName);
}
});
})
);

难点【踩坑】记录:

  • shell进行统计,系统不兼容。只能采用纯git log命令,然后再进行js处理。
  • 使用node的spawn返回log返回日志时,由于vscode缓存限制,只显示20条左右;然后改用了spawnsync同步显示的方法,将数据显示完整,然后进行js 统计
  • 目前使用网络url引用chart插件,还没有找到本地引入js库的方案。【确保网络正常是可以使用,以后有解决方案了再调整】

原文链接:https://juejin.cn/post/7352075755821498394 作者:北鸟南游

(0)
上一篇 2024年3月31日 上午10:37
下一篇 2024年3月31日 上午10:49

相关推荐

发表评论

登录后才能评论