Visual Studio Code はじめての拡張機能開発 2
前回、Visual Studio Code はじめての拡張機能開発として、HelloWorldについて、書きました。
次は、WordCountになります。
単語数をカウントして、ステータスバーに表示する拡張機能です。
もとは、こちらになります。
公式サイト Example - Word Count
ひな形の作成
まずは、ひな形を作成します。
> yo code
? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? WordCount
? What's the identifier of your extension? wordcount
? What's the description of your extension?
? What's your publisher name? helloworld
? Initialize a git repository? Yes
wordcountをVS Codeで開きます。
> cd wordcount
> code .
開発
wordcountは、2ステップで開発をします。
- ステップ1 コマンドパレット編
- コマンドパレットのコマンドを使用したタイミングで、ステータスバーに単語カウントを表示
- ステップ2 エディタのイベントによる、単語カウントの表示
ステップ1 コマンドパレット編
コマンドパレットのコマンドを使用したタイミングで、ステータスバーに単語カウントを表示するようにします。
ひな形を作成すると、コマンドパレットのコマンドを使用して、メッセージを表示するコードが作成されます。
まずは、それを使用して、コマンド実行時にステータスバーに表示する拡張機能をステップ1として、作成します。
コード修正
「src/extension.ts」を修正します。以降、同じファイルを修正していると思って下さい。
修正方針:
- import文の変更。使用するモジュールを詳細に指定します。
- WordCounterクラスを作成して、単語のカウントとステータスバーの表示制御を作ります。この時、markdownファイルのみ、制御が入るようにします。
- activate関数を修正
まずは、import文を変更してみましょう。
import {window, commands, Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextDocument} from 'vscode';
このように変更すると、デフォルトで使用されていた、「vscode」が使えなくなりますので、使用している箇所「vscode.」を削除します。
4行目
import {window, commands, Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextDocument} from 'vscode';
8行目 vscode.ExtensionContext
export function activate(context: ExtensionContext) {
17行目 vscode.commands
let disposable = commands.registerCommand('extension.sayHello', () => {
21行目 vscode.window
window.showInformationMessage('Hello World!');
次に、WordCounterクラスを作成します。
「src/extension.ts」の最後に記述します。
コードに説明を入れておきます。
// WordCounterクラス宣言
class WordCounter {
// ステータスバー保存用のprivate変数定義
private _statusBarItem: StatusBarItem;
public updateWordCounter() {
if (!this._statusBarItem) {
// ステータスバー(左揃え)のリソースを取得。
this._statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
}
// アクティブなエディタを取得
let editor = window.activeTextEditor;
if (!editor) {
// 見つからない場合は、ステータスバーを非表示にして、何もしないでreturn。
this._statusBarItem.hide();
return;
}
// エディタ内のドキュメントを取得
let doc = editor.document;
if (doc.languageId == "markdown") {
// 「.md」のmarkdownの場合に、単語をカウントする(_getWordCount)呼び出し。
let wordCount = this._getWordCount(doc);
// 単語の数をステータスバーに設定して、ステータスバーを表示する。
this._statusBarItem.text = wordCount !== 1 ? `${wordCount} Words` : '1 Word';
this._statusBarItem.show();
} else {
// markdown以外は、ステータスバーを非表示にする
this._statusBarItem.hide();
}
}
// 単語の数を取得する。
public _getWordCount(doc: TextDocument): number {
// ドキュメント内のテキストを取得
let docContext = doc.getText();
// テキストの先頭と最後の2文字以上の空白を削除
docContext = docContext.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
let wordCount = 0;
if (docContext != "") {
// スペースで分割して、分割された数を取得
wordCount = docContext.split(" ").length;
}
return wordCount;
}
// リソース開放用の関数を追加
dispose() {
// ステータスバーのリソースを開放。
this._statusBarItem.dispose();
}
}
次に、activateで、WordCounterを使用できるようにします。
コマンド実行時に、updateWordCounterを実行します。
リソースの開放を設定します。
export function activate(context: ExtensionContext) {
console.log('Congratulations, your extension "wordcount" is now active!');
// WordCounterインスタンス生成
let wordCounter = new WordCounter();
let disposable = commands.registerCommand('extension.sayHello', () => {
// コマンド実行時に、updateWordCount
wordCounter.updateWordCounter();
});
// リソース開放設定
context.subscriptions.push(wordCounter);
context.subscriptions.push(disposable);
}
デバッグ
F5を押して、拡張機能のデバッグができます。
新しいウィンドウが開くので、markdownの新しいファイルを作成して、拡張子「.md」で、保存してみてください。
ショートカットは、新規ファイル作成「ctrl + n」、保存「ctrl + s」です。
aaaa aaaa
bbbb bbbb
「ctrl + shift + p」で、コマンドパレットを開き、「>」がついた状態で、HelloWorldを選択してみてください。
ステータスバーが表示されて、単語の数が表示されているはずです。
すでに、デバッグ状態の「[拡張機能開発ホスト]」ウィンドウがある場合、そのウィンドウをアクティブにして、「ctrl + r」で、拡張機能のリロードが可能です。
ちょっと回り道1/2 (型定義ファイル)
TypeScriptは、「静的型付け」のため、型がコンパイル時に必要となっています。
そこで、他のJavaScriptライブラリを使用するためには、型定義ファイルが必要になっています。
型定義ファイルは、「d.ts」の拡張子で、記述されて、実装がない宣言のみのファイルとなっています。
vscodeの型定義ファイルの「node_modules/vscode/vscode.d.ts」を見てみます。
その中から、vscodeとExtensionContextをピックアップして見ました。
declare namespace vscode {
・・・
export interface ExtensionContext {
subscriptions: { dispose(): any }[];
workspaceState: Memento;
globalState: Memento;
extensionPath: string;
asAbsolutePath(relativePath: string): string;
}
・・・
}
vscodeは、declareが指定されているnamespace宣言になります。
また、「ExtensionContext」は、「export interface」宣言されていることで、外部から呼び出せるように、なっています。
変更前と、変更後の違いは、モジュール全体か、それぞれ個別かになります。
変更前のimportでは、モジュール全体をimportして、使用しています。
import * as vscode from 'vscode';
変更後のimportでは、export宣言されているExtensionContextをimportしています。
import {window, commands, Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextDocument} from 'vscode';
ちょっと回り道2/2 (ExtensionContext.subscriptions)
ExtensionContext.subscriptionsをもう少し掘り下げてみましょう。
「vscode.d.ts」を見ると、dispose関数が入る配列になっています。
「src/extension.ts」の、registerCommand関数の戻り値は、Disposableとなっています。
「vscode.d.ts」にある、Disposableをピックアップしてみました。
export class Disposable {
static from(...disposableLikes: { dispose: () => any }[]): Disposable;
constructor(callOnDispose: Function);
dispose(): any;
}
定義を見てみると、クラス宣言されていて、dispose関数があります。
そのため、指定が可能となっています。
このように、定義を見ると、いろいろできることがありそうです。
ステップ2 エディタのイベントによる、単語カウントの表示
イベントによる、処理を追加していきます。
- onDidChangeTextEditorSelection
- カーソルのポジションが変更された時
- onDidChangeActiveTextEditor
- アクティブエディタが変更された時
まずは、extension.tsにコントロールクラスを追加します。
コードは、以下になります。
class WordCounterController {
private _wordCounter: WordCounter;
private _disposable: Disposable;
constructor(wordCounter: WordCounter) {
this._wordCounter = wordCounter;
this._wordCounter.updateWordCount();
let subscriptions: Disposable[] = [];
window.onDidChangeTextEditorSelection(this._onEvent, this, subscriptions);
window.onDidChangeActiveTextEditor(this._onEvent, this, subscriptions);
// update the counter for the current file
// コメントは、上のようになっているが、この部分は、よくわからない。初めに呼んでいるが、なぜ、二回呼ぶのか。
this._wordCounter.updateWordCount();
this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this._disposable.dispose();
}
private _onEvent() {
this._wordCounter.updateWordCount();
}
}
次に、コマンドパレットのコマンドによるロードと別の方法で実行されるようにします。
activate関数の修正を行います。
export function activate(context: ExtensionContext) {
console.log('Congratulations, your extension "wordcount" is now active!');
// WordCounterインスタンス生成
let wordCounter = new WordCounter();
// WordCounterControllerインスタンス作成
let controller = new WordCounterController(wordCounter);
// WordCounterとWordCounterControllerのリソース開放
context.subscriptions.push(controller);
context.subscriptions.push(wordCounter);
}
package.jsonの修正
コマンド設定の削除を行います。また、
contributes自体を削除するようにします。
"contributes": {
"commands": [{
"command": "extension.sayHello",
"title": "Hello World"
}]
},
コマンドイベントから、Languageイベントに修正します。
"activationEvents": [
"onLanguage:markdown"
],
こうすることで、markdownファイルでの、ロードとイベントの実行が可能になります。
ファイルの拡張子で判断をしているので、ファイルは、保存していないとステータスバーに表示はされません。
ステータスバーをカスタマイズ
ステータスバーをもう少し変更します。
updateWordCount関数にある、ステータスバーに表示する文字列に、$(pencil)を記載します。
this._statusBarItem.text = wordCount !== 1 ? `$(pencil) ${wordCount} Words` : '$(pencil) 1 Word';
こうすることで、鉛筆のアイコンが表示されます。
アイコンは、GitHub Ociconで見ることができます。