VS Code の ChangeLog 用 Extension を作成する

メモ取りを VS Code に移行すると決めたので ChangeLog フォーマット用の環境を整える必要が出てきました。

blog.kondoumh.com

欲しいのは Emacs の組み込み changelog-mode (シンタックスハイライトと項目追加コマンド) 程度の機能です。

Syntax Hilighting

シンタックスハイライトについては、元祖モダンテキストエディタである TextMate で使われていたハイライト定義が後発の Atom や VS Code でもサポートされています。

OrgMode のハイライト定義を頑張って作ってる人はいましたが、ChangeLog 形式のファイルを公開している人は見つかりませんでした。GitHub では ChangeLog が Markdown で書かれている場合が多く必要性が低いと推測されます。そして OSS には ChangeLog がつきものなのでググラビリティも低いです。

ということで、log ファイルのコードハイライトサンプルを参考に作りました。

github.com

後述の VS Code 拡張の開発環境を整えると、Yeomen でジェネレートできる*1のですが、今回は上記のサンプルを編集しました。

changelog.tmLanguage.json: ハイライト定義ファイルの本体です。

{
    "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
    "name": "changelog",
    "patterns": [{
            "include": "#tag"
        },
        {
            "include": "#headline"
        },
        {
            "include": "#list"
        }
    ],
    "repository": {
        "tag": {
            "patterns": [{
                "match": "^\\t(\\*)\\s([^:]+)(:).*",
                "captures": {
                    "1": {
                        "name": "constant.regexp.changelog"
                    },
                    "2": {
                        "name": "markup.changed.changelog"
                    },
                    "3": {
                        "name": "constant.regexp.changelog"
                    }
                }
            }]
        },
        "headline": {
            "patterns": [{
                "match": "^([0-9]{4}-[0-9]{2}-[0-9]{2})\\s+([日月火水木金土])\\s+<([^@]+@[^@]+)>$",
                "captures": {
                    "1": {
                        "name": "markup.deleted.changelog"
                    },
                    "2": {
                        "name": "comment.changelog"
                    },
                    "3": {
                        "name": "entity.name.tag.css.changelog"
                    }
                }
            }]
        },
        "list": {
            "patterns":[{
                "match": "^\\t(\\-)\\s.*$",
                "captures": {
                    "1": {
                        "name": "constant.numeric.changelog"
                    }
                }
            }]
        }
    },
    "scopeName": "source.changelog"
}

日付 + メールアドレスの正規表現を headline、各項目を tag、項目内のリスト要素を list という名前でそれぞれタグ付けして reposiotry に定義し patterns から include しています。

各パターンにマッチしたグループごとに name 属性で色指定をしています。name 属性は TextMate から引き継がれている配色指定のキーになっています。

VS Code の Dark theme ファイル (macOS では下記のパスに格納されています) を VS Code で閲覧しながら適当に指定しました。

/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/theme-defaults/themes/dark_vs.json

ChangeLog は単純な構造なので定義ファイルもコンパクトです。

package.json: ハイライト定義に関するパッケージ情報を定義します。displayName などの属性を設定します。

{
    "name": "changelog-highlight",
    "displayName": "changelog-highlight",
    "description": "A simple extension for changelog syntax highlighting.",
    "version": "0.0.1",
    "publisher": "kondoumh",
    "engines": {
        "vscode": "^1.19.0"
    },
    "categories": [
        "Languages"
    ],
    "contributes": {
        "languages": [{
            "id": "changelog",
            "aliases": ["changelog", "changelog"],
            "extensions": ["ChangeLog"],
            "configuration": "./language-configuration.json"
        }],
        "grammars": [{
            "language": "changelog",
            "scopeName": "source.changelog",
            "path": "./syntaxes/changelog.tmLanguage.json"
        }]
    }
}

language-configuration.json: プログラミング言語固有の設定情報を書きます。ChangeLog は言語というほどのものではありませんが、Emacs の autopair.el のような 自動開閉する bracket の指定があるのでそのまま使うことにしました。

{
    "comments": {
    },
    "brackets": [
        ["{", "}"],
        ["[", "]"],
        ["(", ")"]
    ],
    "autoClosingPairs": [
        ["{", "}"],
        ["[", "]"],
        ["(", ")"],
        ["\"", "\""],
        ["'", "'"]
    ],
    "surroundingPairs": [
        ["{", "}"],
        ["[", "]"],
        ["(", ")"],
        ["\"", "\""],
        ["'", "'"]
    ]
}

launch.json: VS Code 起動時に読み込む設定をします。

// A launch configuration that launches the extension inside a new window
{
    "version": "0.1.0",
    "configurations": [
        {
            "name": "Launch Extension",
            "type": "extensionHost",
            "request": "launch",
            "runtimeExecutable": "${execPath}",
            "args": ["--extensionDevelopmentPath=${workspaceRoot}" ]
        }
    ]
}

以上のファイルを下記構造で ~/.vscode/extensions 配下に置くと VS Code 起動時に読み込んでくれます。

~/.vscode/extensions/changelog-highlight
│  language-configuration.json
│  package.json
│
├─.vscode
│      launch.json
│
└─syntaxes
        changelog.tmLanguage.json

Insert headline Extension

VS Code では日付挿入のような簡単な処理でも Emacs や Vim のように設定ファイルに関数を書いて呼びだすようなことはできず、Extension を作ってインストールするしかないようです。

なんかめんどくさい印象ですが、↓ のチュートリアルに従って Extension のプロジェクトを生成すると案外あっさりできました。拡張を書く障壁を低くするよう頑張ってくれてるみたいです。

code.visualstudio.com

拡張機能は、TypeScript / JavaScript で作成できます*2

以下のように Yeoman と VS Code の ジェネレータを npm で導入し、ジェネレートコマンドを実行すると、コマンドラインで対話的に雛形が作成できます。

$ npm install -g yo generator-code
$ yo code

今回は JavaScript を選択しました。Git リポジトリの初期化も選択すればやってくれます。生成されたプロジェクトを読み込んで、F5 キーを押すかデバッグボタンをクリックすると Extension を読み込んだ状態の VS Code のプロセスが別に起動され、デバッグできます。

package.json: ジェネレート時に指定した displayName などが設定されます。contributes.commands の配列が Extension 使用時にコマンドパレットに表示される コマンド群になります。

{
    "name": "insert-changelog-headline",
    "displayName": "insert-changelog-headline",
    "description": "extension to insert changelog headline",
    "version": "0.0.1",
    "publisher": "kondoumh",
    "engines": {
        "vscode": "^1.19.0"
    },
    "categories": [
        "Other"
    ],
    "activationEvents": [
        "onCommand:extension.insertHeadline"
    ],
    "main": "./extension",
    "contributes": {
        "commands": [{
            "command": "extension.insertHeadline",
            "title": "Insert ChangeLog Headline"
        }]
    },
    "scripts": {
        "postinstall": "node ./node_modules/vscode/bin/install",
        "test": "node ./node_modules/vscode/bin/test"
    },
    "devDependencies": {
        "typescript": "^2.6.1",
        "vscode": "^1.1.6",
        "eslint": "^4.6.1",
        "@types/node": "^7.0.43",
        "@types/mocha": "^2.2.42"
    }
}

extension.js で拡張機能を実装します。ジェネレータではエディタ内で選択されたテキストの文字数をカウントしてポップアップするコードが生成されています。フォーマットした日付とメールアドレスをカーソル位置に挿入するコードを追加しました。

// vscode モジュールを拡張用 API を利用するためにインポート。
const vscode = require('vscode');

// 日付フォーマットのための関数。
function getFormatedDate(date, format) {
    format = format.replace(/YYYY/g, date.getFullYear());
    format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2));
    format = format.replace(/DD/g, ('0' + date.getDate()).slice(-2));
    format = format.replace(/WW/g, ["日", "月", "火", "水", "木", "金", "土"][date.getDay()]);
    return format;
}

// 拡張が activate されるときに実行される関数。
function activate(context) {

    // 拡張が activate される時に出力する診断情報を出力。
    console.log('Congratulations, your extension "insert-changelog-headline" is now active!');

    // 拡張機能の本体を実装して登録する。package.json に設定した commandId にマッチする commandId を指定する必要がある。
    let disposable = vscode.commands.registerCommand('extension.insertHeadline', function() {

        var editor = vscode.window.activeTextEditor;
        if (!editor) {
            return;
        }
        var headline = getFormatedDate(new Date(), 'YYYY-MM-DD WW') + '  <kondoh@local>'
        var selection = editor.selection;

        editor.edit((editorEdit) => {
            editorEdit.replace(selection, '');
            editorEdit.insert(selection.active, headline);
        });
    });

    context.subscriptions.push(disposable);
}
exports.activate = activate;

// 拡張が deactivate される時に実行される関数。
function deactivate() {}
exports.deactivate = deactivate;

デバッグが完了しコードが完成したらプロジェクトフォルダを .vscode 配下にコピーし VS Code を再起動すると extension が読み込まれた状態になります。 コマンドパレットから呼び出して、headline を挿入できるようになります。

f:id:kondoumh:20180112065116p:plain

もちろんパッケージを作って Marketplace で公開することも可能です。

marketplace.visualstudio.com

ということで、VS Code で快適に ChangeLog メモを書けるようになりました。

VS Code の Extension は Node.js のエコシステムで開発できるので汎用的なスキルセットが使えて色々高度な自動化ができそうです。

あと Emacs Friendly Keymap を入れるとかなり Emacs です。

marketplace.visualstudio.com

*1:テンプレートに Coloer Theme が用意されています。

*2:ATOM エディタは CoffeeScript でしたね。