VSCode拡張機能を作ってみる2 - 入力補完

# はじめに

[公式サンプル](https://github.com/microsoft/vscode-extension-samples)にVSCode APIを使ったサンプルがあるので、いくつか使いそうなものをピックアップしてみました。

今回は、`CompletionItemProvider` API を使って入力補完する機能を付けます。

https://github.com/Microsoft/vscode-extension-samples/tree/main/completions-sample


前回
[VSCode拡張機能を作ってみる1 - インストールとHelloWorld!](https://qiita.com/qvtec/items/53ec5a7dd473142c69c1)

# 事前準備

まず、空の`completion.ts`ファイルを作成し、`extension.ts` で読み込んでおきます。

``` ts:completion.ts
import * as vscode from 'vscode';

export default class Completions implements vscode.CompletionItemProvider
{
    public provideCompletionItems(): vscode.ProviderResult<vscode.CompletionItem[]>
    {
        // ここに補完機能を追加していきます
        return [];
    }
}
```

plaintextにしているので対応したい言語に変更してください。

``` ts:extension.ts
import * as vscode from 'vscode';

import Completions from "./completions";

export function activate(context: vscode.ExtensionContext)
{
    const provider = vscode.languages.registerCompletionItemProvider('plaintext', new Completions());
    context.subscriptions.push(provider);
}

export function deactivate() {}
```

# Hello World!

シンプルに`Hello World!`を自動補完してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536619/aef23830-8ba2-88df-dfe3-097a82528e01.png)


``` ts:completion.ts
    public provideCompletionItems(): vscode.ProviderResult<vscode.CompletionItem[]>
    {
        const simpleCompletion = new vscode.CompletionItem('Hello World!');
        return [simpleCompletion];
    }
```

# insertText

挿入テキストを指定できます。
`insertText`がない場合は`vscode.CompletionItem`のラベルが挿入されます。


``` ts:completion.ts
    public provideCompletionItems(): vscode.ProviderResult<vscode.CompletionItem[]>
    {
        const simpleCompletion = new vscode.CompletionItem('Hello World!');
        simpleCompletion.insertText = 'Change the World!';

        return [simpleCompletion];
    }
```

# documentation

補完候補に説明を追加できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536619/62d73310-a0b2-06de-fdac-8bdfb54bd30b.png)

``` ts:completion.ts
    public provideCompletionItems(): vscode.ProviderResult<vscode.CompletionItem[]>
    {
        const snippetCompletion = new vscode.CompletionItem('Good part of the day');
        snippetCompletion.documentation = new vscode.MarkdownString("Inserts a snippet that lets you select the _appropriate_ part of the day for your greeting.");

        return [snippetCompletion];
    }
```

# Snippet

`SnippetString`を使って挿入テキストにスニペットを追加できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536619/75261151-7a25-dddd-8f91-8667a6dbd6a0.png)

``` ts:completion.ts
    public provideCompletionItems(): vscode.ProviderResult<vscode.CompletionItem[]>
    {
        const snippetCompletion = new vscode.CompletionItem('Good part of the day');
        snippetCompletion.insertText = new vscode.SnippetString('Good ${1|morning,afternoon,evening|}. It is ${1}, right?');

        return [snippetCompletion];
    }
```

# commitCharacters

補完候補が1つまたは選択している場合、特定の文字を入力すると自動で補完が完了します。

この状態で「.」を入力すると「console.」と補完されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536619/0c240cbe-d0d5-7c8a-f8bf-22e123c1df64.png)

``` ts:completion.ts
    public provideCompletionItems(): vscode.ProviderResult<vscode.CompletionItem[]>
    {
        const commitCharacterCompletion = new vscode.CompletionItem('console');
        commitCharacterCompletion.commitCharacters = ['.'];
        commitCharacterCompletion.documentation = new vscode.MarkdownString('Press `.` to get `console.`');

        return [commitCharacterCompletion];
    }
```

# command

`IntelliSense`を再トリガーする補完アイテムを使ってみます。

「new」を自動補完した後、続けて同じ入力補完を実行できるようになります。
`insertText`では「new」の後ろにスペースを追加しています。
Enterを押し続けると「new new new...」と連続で挿入できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536619/8e59df9b-5816-38af-91d4-f312fe32c4da.png)

``` ts:completion.ts
    public provideCompletionItems(): vscode.ProviderResult<vscode.CompletionItem[]>
    {
        const commandCompletion = new vscode.CompletionItem('new');
        commandCompletion.insertText = 'new ';
        commandCompletion.command = { command: 'editor.action.triggerSuggest', title: 'Re-trigger completions...' };

        return [commandCompletion];
    }
```

# triggerCharacters

特定の文字が入力されたときだけ入力補完する場合は、CompletionItemProviderの3引数目以降に追加していきます。
「.」が入力された場合だけ実行する場合は
`registerCompletionItemProvider('plaintext', new Completions(), '.');`
となります。

``` ts:extension.ts
export function activate(context: vscode.ExtensionContext)
{
    const provider = vscode.languages.registerCompletionItemProvider('plaintext', new Completions(), '.');
    context.subscriptions.push(provider);
}
```

# icon

completionsのtypeを指定することで補完候補のアイコンを指定することができます。

``` ts
const commandCompletion = new vscode.CompletionItem('new');
commandCompletion.kind = vscode.CompletionItemKind.Keyword;
```

https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions

# TextDocument

入力した内容を取得し処理を追加することができます。

`provideCompletionItems`の引数に`vscode.TextDocument`と`vscode.Position`を追加し、挿入した行を取得します。
「console.」で終わる文字だった場合にのみ、`log,warn,error`の入力補完を追加します。

``` ts:completion.ts
    public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.ProviderResult<vscode.CompletionItem[]>
    {
        const linePrefix = document.lineAt(position).text.substr(0, position.character);
        if (!linePrefix.endsWith('console.')) {
            return undefined;
        }

        return [
            new vscode.CompletionItem('log', vscode.CompletionItemKind.Method),
            new vscode.CompletionItem('warn', vscode.CompletionItemKind.Method),
            new vscode.CompletionItem('error', vscode.CompletionItemKind.Method),
        ];
    }
```

# まとめ

入力文字取得してごにょごにょしてinsertTextするだけで便利なことできそう！

次回
[SCode拡張機能を作ってみる3 - 文字の装飾](https://qiita.com/qvtec/items/91e7523d832d41aee5df)