Help us understand the problem. What is going on with this article?

Visual Studio Code はじめての拡張機能開発 2

More than 3 years have passed since last update.

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」を修正します。以降、同じファイルを修正していると思って下さい。

修正方針:

  1. import文の変更。使用するモジュールを詳細に指定します。
  2. WordCounterクラスを作成して、単語のカウントとステータスバーの表示制御を作ります。この時、markdownファイルのみ、制御が入るようにします。
  3. 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で見ることができます。

rma
JavaのWEBアプリケーションエンジニアです。 最近ハマってるのは、Visual Studio CodeとOSに関して、調べること。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away