vscode是个超级好用的开发工具,谁用谁知道。
背景故事
一个项目开发、维护的时间久了之后,总会多多少少碰到一段不是你写的,而现在你要维护,但你却看不明白的那是什么鬼的代码;当然有时候也可能是多人在同一个项目里协作开发时,对于bug最终责任人的确诊问题(找到最终责任人不是要“修理他”。帮助他认识问题,提高自身能力,加深团队协作意识才是重点)。
大多数人都用git
,也知道git里诸如:git log
,git blame
等命令都可以帮我们做到以上需求,可每次看到一段代码之后,先从IDE里切换到Terminal下,然后敲打命令,并且要记好事故代码的行号,以及准确的文件路径,敲来敲去还是挺烦人的。
于是,能在不来回切换工作环境的情况下,迅速找到某一段代码的作者、写作时间、写作目的就显的还是有点用了。
一个扩展的诞生
写作思路
为了更直观表达这个扩展的设计思路,我用了一个图:
后面我们就根据图里描述的思路来开展工作。
获取信息
要得到一段指定行号(行区间)代码的信息,大家都知道用git blame
,展示结果如下:
»git blame -L 10,11 js/index.js
297cb0df (Howard.Zuo 2017-03-08 21:58:03 +0800 10) render(h) {
297cb0df (Howard.Zuo 2017-03-08 21:58:03 +0800 11) return h(Game);
少了,"写作目的",也就是说,这个命令的结果无法告诉我们作者提交这段代码时的commit message
写了什么。于是我得翻翻git blame --help
,可喜的是被我找到了--line-porcelain
选项,在她的描述里看到了这么一句:but output commit information for each line
。好像有戏,来试试看:
»git blame -L 10,11 js/index.js --line-porcelain
297cb0df8ab1fe06ee935798d1a2dd4e712a070d 10 10 2
author Howard.Zuo
author-mail <leftstick@qq.com>
author-time 1488981483
author-tz +0800
committer Howard.Zuo
committer-mail <leftstick@qq.com>
committer-time 1488981503
committer-tz +0800
summary fix duplicate key issue
previous 43966e5cc4c70998c265781fe8acf02946c28f6e js/index.js
filename js/index.js
render(h) {
297cb0df8ab1fe06ee935798d1a2dd4e712a070d 11 11
author Howard.Zuo
author-mail <leftstick@qq.com>
author-time 1488981483
author-tz +0800
committer Howard.Zuo
committer-mail <leftstick@qq.com>
committer-time 1488981503
committer-tz +0800
summary fix duplicate key issue
previous 43966e5cc4c70998c265781fe8acf02946c28f6e js/index.js
filename js/index.js
return h(Game);
不错,这明显是个可以被解析的数据结构。所以获取信息就靠她了git blame -L <start,end> <filePath> --line-porcelain
。
因为vscode是一个基于electron开发的IDE,所以node.js
的API对我们是可用的,于是可以通过如下代码执行上面的命令,并且拿到结果数据:
//vscode api,获取当前正在操作的编辑页面
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('You have to active a file first');
return;
}
//拿到鼠标选择的内容
const selection = editor.selection;
//合成git命令
const cmd = `git blame -L ${selection.start.line + 1},${selection.end.line + 1} ${editor.document.fileName} --line-porcelain`;
//通过child_press执行该命令,
child_process.exec(cmd, {
cwd: vscode.workspace.rootPath
}, (error, stdout, stderr) => {
//这里的stdout就是上面我们看到的输出内容了
});
解析信息
看了上面的输出内容,我们需要一个model来描述一个这样一个条目:
export interface Item {
hash: string;
shortHash: string;
author: string;
authorEmail: string;
authorTime: number;
authorTz: string;
committer: string;
committerEmail: string;
committerTime: number;
committerTz: string;
commitMessage: string;
previousCommit?: string;
fileName: string;
change: string;
}
接下来就是如何解析git
命令的输出结果了,一个大大的循环来搞定:
export function parse(output: string): Array<Item> {
const lines = output.replace(/\r\n/mg, '\n').split('\n');
const commits: Array<Item> = [];
let commit;
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i];
//一个item的开始标志就是commit的hash
if (/^[a-z0-9]{15,}/.test(line)) {
commit = {};
commits.push(commit);
commit.hash = line.split(' ')[0];
commit.shortHash = commit.hash.substring(0, 8);
} else if (/^author\s/.test(line)) {
commit.author = line.split(' ')[1];
} else if (/^author-mail\s/.test(line)) {
commit.authorEmail = line.split(' ')[1];
} else if (/^author-time\s/.test(line)) {
commit.authorTime = +line.split(' ')[1];
} else if (/^author-tz\s/.test(line)) {
commit.authorTz = line.split(' ')[1];
} else if (/^committer\s/.test(line)) {
commit.committer = line.split(' ')[1];
} else if (/^committer-mail\s/.test(line)) {
commit.committerEmail = line.split(' ')[1];
} else if (/^committer-time\s/.test(line)) {
commit.committerTime = +line.split(' ')[1];
} else if (/^committer-tz\s/.test(line)) {
commit.committerTz = line.split(' ')[1];
} else if (/^previous\s/.test(line)) {
commit.previousCommit = line.split(' ')[1];
} else if (/^filename\s/.test(line)) {
commit.fileName = line.split(' ')[1];
} else if (!commit.fileName) {
commit.commitMessage = line;
} else {
commit.change = line;
}
}
return commits;
}
展示
有了上面解析完毕的Array<Item>
,简单转换一下字符串,对任何人都是没什么难度的:
export function pretty(commits: Array<Item>): string {
return commits.map(c => {
return `${c.shortHash} ${c.author} ${formatDate(c.authorTime * 1000)} "${c.commitMessage}" ${c.change}`;
})
.join('\n');
}
解决了所有核心问题,剩下的就是按照vscode-docs,动手补充一个扩展需要的额外因素了。
安装代码骨架生成器
npm install -g yo generator-code
生成插件项目
yo code
在提问中,依次回答所有问题,最后项目骨架生成。
生成右键菜单
修改package.json
,增加/修改contributes
字段:
"contributes": {
"menus": {
"editor/context": [{
"command": "extension.gitblame",
"group": "sourcecontrol",
"when": "config.git.enabled && scmProvider == git && gitState == idle"
}]
},
"commands": [{
"command": "extension.gitblame",
"title": "Git blame",
"key": "ctrl+b"
}],
"keybindings": [{
"command": "extension.gitblame",
"key": "alt+b"
}]
}
于是乎,一个右键选项Git blame就会出现在你选择一段代码后的右键菜单里
懒加载扩展
一个扩展根据需求,并不一定要随vscode
的启动而启动,那样影响vscode
的整体性能,而且没什么特别的好处。所以我们要懒加载。修改package.json
,增加/修改activationEvents
字段:
"activationEvents": [
"onCommand:extension.gitblame"
]
只有当用户使用
extension.gitblame
这个命令时(也就是在右键菜单里选择了Git blame时),该扩展才正式激活。
到此为止,这个扩展就基本完成了。效果如下:
扩展下载地址:vscode-git-blamer
项目源码地址:vscode-git-blamer
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。