爲vscode編寫擴展

vscode是個超級好用的開發工具,誰用誰知道。javascript

背景故事

一個項目開發、維護的時間久了以後,總會多多少少碰到一段不是你寫的,而如今你要維護,但你卻看不明白的那是什麼鬼的代碼;固然有時候也多是多人在同一個項目裏協做開發時,對於bug最終責任人的確診問題(找到最終責任人不是要「修理他」。幫助他認識問題,提升自身能力,加深團隊協做意識才是重點)。java

大多數人都用git,也知道git裏諸如:git loggit blame等命令均可以幫咱們作到以上需求,可每次看到一段代碼以後,先從IDE裏切換到Terminal下,而後敲打命令,而且要記好事故代碼的行號,以及準確的文件路徑,敲來敲去仍是挺煩人的。node

因而,能在不來回切換工做環境的狀況下,迅速找到某一段代碼的做者、寫做時間、寫做目的就顯的仍是有點用了。git

一個擴展的誕生

寫做思路

爲了更直觀表達這個擴展的設計思路,我用了一個圖:github

圖片描述

後面咱們就根據圖裏描述的思路來開展工做。typescript

獲取信息

要獲得一段指定行號(行區間)代碼的信息,你們都知道用git blame,展現結果以下:npm

»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。好像有戲,來試試看:json

»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-porcelainapi

由於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

相關文章
相關標籤/搜索