Home > Editor > Visual Studio Code > ドキュメント

例 -言語サーバー

例 -言語サーバー

Example - Language Server(原文)

言語サーバは、VS Codeで、開いているファイルに、あなた独自の妥当性検証ロジックを追加することができます。 一般的に、あなたは、単に、開発言語を検証します。 しかしながら、他のファイルタイプを検証することは、同様に役に立ちます。 言語サーバは、例えば、不適切な言語のファイルを確認することができます。

一般に、プログラミング言語の検証をすることは、コストがかかります。 特に、妥当性検証が必要とするとき、複数のファイルを解析し、そして、抽象構文ツリーを構築します。 その処理能力コストを回避するために、VS Codeの言語サーバは、別々のプロセス内で実行されます。 また、このアーキテクチャは、それを可能にします。 言語サーバは、TypeScript/JavaScriptを基盤とする他の言語で、記述することができます。 そして、それらは、コードの補完やFind All Referenceのようなハイコストの、 追加された言語機能をサポートすることができます。

残りの文書は、あなたが、VS Codeの通常の拡張開発に精通していることを想定しています。

あなた独自の言語サーバを実装します

Implement your own Language Server

言語サーバは、どんな言語でも実装することができます。 しかしながら、今、VS Codeは、Node.jsのライブラリだけを提供します。 追加されたライブラリは、将来に続きます。 Node.jsの言語サーバ実装の優れた出発点は、例えば、リポジトリ・言語サーバノードの例です。

リポジトリのクローンを作成し、続いて、以下を実行します。:


> npm install
> code .
> cd ../server
> npm install
> code .

'Client'を説明する

Explaining the 'Client'

クライアントは、実際に、通常のVS Codeの拡張です。 それには、作業スペース・フォルダのルートのpackage.jsonファイルが含まれています。 そのファイルの3つの興味深い項目があります。

まず、activationEventsを見ます:


"activationEvents": [
    "onLanguage:plaintext"
]

この項目では、拡張をアクティブにするために、 プレーンテキストファイルが開かれるとすぐに、VS Codeに伝えます。(例えば、拡張子.txtによるファイル)。

次に、設定項目を見ます:


"configuration": {
    "type": "object",
    "title": "Example configuration",
    "properties": {
        "languageServerExample.maxNumberOfProblems": {
            "type": "number",
            "default": 100,
            "description": "Controls the maximum number of problems produced by the server."
        }
    }
}

この項目は、VS Codeに設定設定を与えます。 例は、これらの設定を、スタートアップ上のどんな設定の変更でも言語サーバを通して、どのように、送信するかを説明します。

最後の部分は、vscodeの言語クライアントライブラリに、依存関係を追加します。:


"dependencies": {
    "vscode-languageclient": "0.10.x"
}

上で述べたように、クライアントは、通常のVS Code拡張として実装されています。

下記は、対応するextension.tsファイルの内容です:


/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.

著作権(c)マイクロソフトコーポレーション。
すべての著作権を所有しています。
MITライセンスの下で認可されます。
ライセンス情報については、プロジェクトのルートにあるLicense.txtを参照してください。
 * ------------------------------------------------------------------------------------------ */
'use strict';

import * as path from 'path';

import { workspace, Disposable, ExtensionContext } from 'vscode';
import { LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions } from 'vscode-languageclient';

export function activate(context: ExtensionContext) {

    // The server is implemented in node
    // サーバは、ノードで実装されています
    let serverModule = context.asAbsolutePath(path.join('server', 'server.js'));
    // The debug options for the server
    // サーバのデバッグオプション
    let debugOptions = { execArgv: ["--nolazy", "--debug=6004"] };
    
    // If the extension is launch in debug mode the debug server options are use
    // Otherwise the run options are used
    //拡張が、デバッグモードで起動する場合、デバッグ・サーバ・オプションが、使用されます。
    // それ以外の場合には、実行オプションが、使用されます。
    let serverOptions: ServerOptions = {
        run : { module: serverModule },
        debug: { module: serverModule, options: debugOptions }
    }
    
    // Options to control the language client
    // オプションは、言語クライアントを制御します
    let clientOptions: LanguageClientOptions = {
        // Register the server for plain text documents
        // プレーン・テキスト・ドキュメントのためのサーバを登録します
        documentSelector: ['plaintext'],
        synchronize: {
            // Synchronize the setting section 'languageServerExample' to the server
            // 設定項目『languageServerExample』をサーバに同期させます
            configurationSection: 'languageServerExample',
            // Notify the server about file changes to '.clientrc files contain in the workspace
            // 作業スペースに含まれている'.clientrcファイルに、ファイルの変更について、サーバに、通知します
            fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
        }
    }
    
    // Create the language client and start the client.
    // 言語クライアントを作成し、クライアントを起動します。
    let disposable = new LanguageClient('Language Server Example', serverOptions, clientOptions).start();
    
    // Push the disposable to the context's subscriptions so that the 
    // client can be deactivated on extension deactivation
    // コンテクストの購読に破棄可能なものをプッシュします。
    // そのため、クライアントは、拡張の無効で、無効にすることができます。
    context.subscriptions.push(disposable);
}

『サーバ』を説明する

Explaining the 'Server'

例の中では、また、サーバは、TypeScriptで実装され、Node.jsを使用して実行されます。 VS Codeが、既に、Node.jsランタイムに運んでいます。 実行時に、あなたが、極めて特定の必要条件がない限り、あなた自身を提供する必要はありません。

サーバのpackage.jsonファイルの面白い項目は、以下の通りです:


"dependencies": {
    "vscode-languageserver": "0.10.x"
}

これは、vscode言語サーバライブラリをプルします。

以下は、常に、VS Codeから、サーバまで、ファイルの完全な内容を送信することで、 テキスト文書を同期させる、提供された簡単なテキスト文書マネージャーを使用するサーバーの実装です。


/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.

著作権(c)マイクロソフトコーポレーション。
すべての著作権を所有しています。
MITライセンスの下で認可されます。
ライセンス情報については、プロジェクトのルートにあるLicense.txtを参照してください。

 * ------------------------------------------------------------------------------------------ */
'use strict';

import {
    createConnection, IConnection,
    TextDocuments, ITextDocument, Diagnostic,
    InitializeParams, InitializeResult
} from 'vscode-languageserver';

// Create a connection for the server. The connection uses 
// stdin / stdout for message passing
// サーバのために、接続を作成します。
// 接続は、メッセージ受渡しのために、stdin / stdoutを使用します。
let connection: IConnection = createConnection(process.stdin, process.stdout);

// Create a simple text document manager. The text document manager
// supports full document sync only
// 簡単な、テキスト文書マネージャーを作成します。
// テキスト文書マネージャーは、完全な文書の同期だけをサポートします。
let documents: TextDocuments = new TextDocuments();
// Make the text document manager listen on the connection
// for open, change and close text document events
// テキスト文書マネージャーは、開くために、接続を監視します。
// テキスト文書のイベントを変更し、そして、閉じます。
documents.listen(connection);

// After the server has started the client sends an initilize request. The server receives
// in the passed params the rootPath of the workspace plus the client capabilites. 
// サーバが、起動したあと、クライアントは、初期化要求を送信します。
// サーバは、クライアント機能を加えて、渡されたパラメータで、作業スペースのrootPathを受け取ります。
let workspaceRoot: string;
connection.onInitialize((params): InitializeResult => {
    workspaceRoot = params.rootPath;
    return {
        capabilities: {
            // Tell the client that the server works in FULL text document sync mode
            // サーバが、完全なテキスト文書の同期モードで、動作するためにクライアントに指示します。
            textDocumentSync: documents.syncKind
        }
    }
});

// Listen on the connection
//接続を監視します
connection.listen();

簡単な妥当性検証を追加する

Adding a Simple Validation

文書の妥当性検証をサーバに追加するために、私たちは、単純に、テキスト文書マネージャーのリスナーを追加します。 それは、内容のテキスト文書の変更されるたびに、呼び出されます。 それは、続いて、サーバー次第で、決定する最も良い時間は、文書を検証するときです。 例えば、実装において、サーバは、プレーン・テキスト文書を検証します。 そして、そのTypeScriptでつづるために、メッセージとTypeScriptのすべての実現値にフラグを立てます。 対応するコード・スニペットは、次のようになります。:


// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
// テキスト文書の内容が変更されました。
// このイベントは、テキスト文書が、最初に開かれたとき、あるいは、その内容が、変更されたとき、送られます。
documents.onDidChangeContent((change) => {
    let diagnostics: Diagnostic[] = [];
    let lines = change.document.getText().split(/\r?\n/g);
    lines.forEach((line, i) => {
        let index = line.indexOf('typescript');
        if (index >= 0) {
            diagnostics.push({
                severity: DiagnosticSeverity.Warning,
                range: {
                    start: { line: i, character: index},
                    end: { line: i, character: index + 10 }
                },
                message: `${line.substr(index, 10)} should be spelled TypeScript`
            });
        }
    })
    // Send the computed diagnostics to VS Code.
    // 計算された診断をVS Codeに送信します。
    connection.sendDiagnostics({ uri: change.document.uri, diagnostics });
});

診断のヒントとコツ!

Diagnostics Tips and Tricks!

  • 開始と終了の位置が同じ場合、VS Codeは、単語をその位置で波線にするでしょう。
  • あなたが、行の末端まで波線にしたい場合、続いて、終了位置の文字を、Number.MAX_VALUEに設定します。

検証するために、言語サーバは、次の事を行います。:

  • (上に示した)サーバ・コードが含まれるVS Codeのインスタンスへ移動しまします。 そして、ビルド・タスクを開始するために、Ctrl+Shift+Bを押します。 タスクは、サーバ・コードをコンパイルし、そして、それを、機能拡張フォルダにインストール(例えばコピー)します。
  • 次に、拡張(クライアント)とVS Codeのインスタンスに戻ります。 そして、拡張コードを実行するVS Codeの追加された拡張開発ホスト・インスタンスを起動するために、F5を押します。
  • ルート・フォルダ内にtest.txtファイルを作成します。そして、次に示す内容を貼り付けます:

typescript lets you write JavaScript the way you really want to.
typescript is a typed superset of JavaScript that compiles to plain JavaScript.
Any browser. Any host. Any OS. Open Source.

TypeScriptは、あなたが、本当に望む方法で、JavaScriptを記述することができます。 TypeScriptは、普通のJavaScriptにコンパイルする、 JavaScriptの型指定された上位版です。どんなブラウザでも。 どんなホストでも。どんなOSでも。ソースを開きます。

拡張開発ホスト・インスタンスは、次のようになります。:

拡張開発ホスト・インスタンス

クライアントとサーバをデバッグする

Debugging both Client and Server

クライアント・コードのデバッグは、通常の拡張をデバッグするように、簡単です。 単純に、ブレークポイントをクライアント・コードが含まれているVS Codeのインスタンスに設定します。 そして、F5を押すことで、拡張をデバッグします。 拡張を立ち上げて、デバッグすることについての詳しい説明は、あなたの拡張を実行し、デバッグするを参照してください。

F5を押すことで、拡張をデバッグします。

サーバは、拡張(クライアント)内で、実行されているLanguageClientによって開始されるため、 私たちは、デバッガを、実行サーバに添付する必要があります。 そのためには、サーバ・コードが含まれているVS Codeのインスタンスに切り替え、F5を押します。 これは、デバッガをサーバに添付するでしょう。実行しているサーバと対話するために、通常のDebug Viewを使用します。

デバッガをサーバに添付する

サーバで、設定設定を使用します。

Using Configuration Settings in the Server

拡張のクライアントの一部を記述するとき、私たちは、すでに、問題の最大数を伝えて制御するために、設定を定義しています。 また、私たちは、syncronization設定を使用して、これらの設定を、サーバに同期するために、LanguageClientに指示しました。


    // Synchronize the setting section 'languageClientExample' to the server
    // 設定項目『languageClientExample』をサーバに同期させます
    configurationSection: 'languageServerExample',
    // Notify the server about file changes to '.clientrc files contain in the workspace
    // 作業スペースに含まれている'.clientrcファイルに、ファイルの変更について、サーバに、通知します

    fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
}

私たちが、次に、必要なことは、そして、設定の変更が、開いたテキスト文書を再検証される場合、 サーバー側で構成変更を監視するだけです。 文書の変更イベント処理の検証ロジックを再利用するために、 私たちは、validateTextDocument関数にコードを抽出します。 そして、maxNumberOfProblems変数を引き受けるために、コードを修正します:


function validateTextDocument(textDocument: ITextDocument): void {
    let diagnostics: Diagnostic[] = [];
    let lines = textDocument.getText().split(/\r?\n/g);
    let problems = 0;
    for (var i = 0; i < lines.length && problems < maxNumberOfProblems; i++) {
        let line = lines[i];
        let index = line.indexOf('typescript');
        if (index >= 0) {
            problems++;
            diagnostics.push({
                severity: DiagnosticSeverity.Warning,
                range: {
                    start: { line: i, character: index},
                    end: { line: i, character: index + 10 }
                },
                message: `${line.substr(index, 10)} should be spelled TypeScript`
            });
        }
    }
    // Send the computed diagnostics to VS Code.
    // 計算された診断をVS Codeに送信します。
    connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
}

接続の設定を変更するのための通知ハンドラを追加することで、 構成変更の処理が、行われます。対応するコードは、次のようになります。:


// The settings interface describe the server relevant settings part
interface Settings {
// 設定インターフェイスは、設定部分に関連するサーバを説明します。
    languageServerExample: ExampleSettings;
}

// These are the example settings we defined in the client's package.json
// file
// これらは、私たちがクライアントのpackage.jsonファイルで定義した例の設定です。
interface ExampleSettings {
    maxNumberOfProblems: number;
}

// hold the maxNumberOfProblems setting
// maxNumberOfProblems設定を保持します。
let maxNumberOfProblems: number;
// The settings have changed. Is send on server activation
// as well.
// 設定は変更しました。
// 同様に、アクティブ化されたサーバに送信します。
connection.onDidChangeConfiguration((change) => {
    let settings = <Settings>change.settings;
    maxNumberOfProblems = settings.languageServerExample.maxNumberOfProblems || 100;
    // Revalidate any open text documents
    // Revalidateは、どんなテキスト文書でも開きます。
    documents.all().forEach(validateTextDocument);
});

あらためて、クライアントを起動し、以下の妥当性検証で、レポート1の問題の結果を最大に設定を変更します。:

レポート1の問題の結果を最大に設定を変更します。

追加された言語機能を追加する

Adding additional Language Features

通常、言語サーバが実装する最初の興味深い機能は、文書の妥当性検証です。 その意味で、リンターでさえ、言語サーバとして、計算され、そして、VS Codeでは、リンターは、 通常言語サーバとして実装されます。(eslintとjshintのための例を参照してください)。 しかし、更に多くの言語サーバがあります。それらは、コードの補完を提供することができます。 すべての参照を検索し、そして、定義に移動します。 下記のサンプル・コードは、サーバに、コードの補完を追加します。 それは、2つの単語『TypeScript』と『JavaScript』を単純に提案します。


// This handler provides the initial list of the completion items.
// このハンドラは、補完項目の最初のリストを提供します。
connection.onCompletion((textDocumentPosition: TextDocumentIdentifier): CompletionItem[] =&gt; {
    // The pass parameter contains the position of the text document in 
    // which code complete got requested. For the example we ignore this
    // info and always provide the same completion items.
    // パス・パラメータには、コードの補完が要求された、テキスト文書の位置が含まれています。
    // サンプルのため、私たちは、この情報、そして、常に提供される同じ補完項目を無視します
    return [
        {
            label: 'TypeScript',
            kind: CompletionItemKind.Text,
            data: 1
        },
        {
            label: 'JavaScript',
            kind: CompletionItemKind.Text,
            data: 2
        }
    ]
});

// This handler resolve additional information for the item selected in
// the completion list.
// このハンドラは、補完リストで選択する項目のための追加の情報を解決します。
connection.onCompletionResolve((item: CompletionItem): CompletionItem =&gt; {
    if (item.data === 1) {
        item.detail = 'TypeScript details',
        item.documentation = 'TypeScript documentation'
    } else if (item.data === 2) {
        item.detail = 'JavaScript details',
        item.documentation = 'JavaScript documentation'
    }
    return item;
});

データフィールドは、解決ハンドラで、補完項目を識別するために、一意で使用されます。 データ・プロパティは、プロトコルのために分かりやすいです。メッセージを渡す基盤となるプロトコルは、 JSONに、そして、からシリアライズできる、 SONに基づいているデータフィールドが、データだけを保持している必要があります。

不足しているのは、サーバ・サポート・コードの補完を、要求するVS Codeに伝えることだけです。 行うために、それで、初期化ハンドラに対応する機能にフラグを立てます:


connection.onInitialize((params): InitializeResult => {
    ...
    return {
        capabilities: {
            ...
            // Tell the client that the server support code complete
            // サーバが、コードの補完をサポートすることをクライアントに伝えます
            completionProvider: {
                resolveProvider: true
            }
        }
    }
});

下記のスクリーン・ショットは、計画テキストファイルのコードの補完を示します。:

計画テキストファイルのコードの補完

追加された言語サーバ機能

Additional Language Server features

次の言語機能は、現在、他のコード補完の言語サーバで、サポートされています。:

  • 文書の強調表示:テキスト文書で、すべての『同じ』記号を強調します。
  • ホバー:テキスト文書内で、選択される記号のための、ホバー情報を提供します。
  • 署名ヘルプ:テキスト文書で選択される記号のための、署名ヘルプを提供します。
  • 定義に移動します:テキスト文書内で選択した記号のための、定義に移動するサポートを提供します。
  • 参照を検索します。:テキスト文書内で、選択されている記号のために、すべてのプロジェクト全体の参照を検索します。
  • 文書の記号を一覧にする:テキスト文書で定義されるすべての記号を一覧にします。
  • 作業スペースの記号を一覧にする:すべてのプロジェクト全体の記号の一覧を示します。

インクリメンタル・テキスト文書の同期

Incremental Text Document Synchronization

例は、VS Codeと言語サーバの間で、文書を同期させるvscode言語サーバモジュールで提供される、 簡単なテキスト文書マネージャーを使用します。これは、2つの欠点を持っています。:

  • テキスト文書の全体の内容からの大量のデータ転送は、繰り返しサーバに送信されます。
  • 既存の言語ライブラリが、使用される場合、そのようなライブラリは、通常、不必要な構文解析を避けるために、 インクリメンタル文書の更新、そして、抽象構文ツリーの作成をサポートしています。

したがって、同様に、プロトコルは、インクリメンタル文書の同期をサポートしています。 インクリメンタル文書の同期の使用するために、サーバは、3つの通知ハンドラをインストールする必要があります。:

  • onDidOpenTextDocument:テキスト文書がVS Codeで開かれたとき、呼び出されます。
  • onDidChangeTextDocument:テキスト文書の内容が、VS Codeで変更されるとき、呼び出されます。
  • onDidCloseTextDocument:テキスト文書がVS Codeで閉じられたとき、呼び出されます。

下記のコード・スニペットは、これらの通知ハンドラを、接続に、どのように、フックするか、 そして、適切な機能を、初期化に、どのように、返すかを説明します。:


connection.onInitialize((params): InitializeResult => {
    ...
    return {
        capabilities: {
            // Enable incremental document sync
            // インクリメンタル文書の同期を有効にします。
            textDocumentSync: TextDocumentSyncKind.Incremental,
            ...
        }
    }
});

connection.onDidOpenTextDocument((params) => {
    // A text document got opened in VS Code.
    // params.uri uniquely identifies the document. For documents store on disk this is a file URI.
    // params.text the initial full content of the document.
    // テキスト文書は、VS Codeで開かれました。params.uriは、一意で、文書を識別します。
    // ディスク上の文書ストアのための、これは、ファイルURIです。
    // params.text 最初の文書の完全な内容。
});

connection.onDidChangeTextDocument((params) => {
    // The content of a text document did change in VS Code.
    // params.uri uniquely identifies the document.
    // params.contentChanges describe the content changes to the document.
    // テキスト文書の内容は、VS Codeで変更されました。
    // params.uriは、一意で、文書を識別します。
    // params.contentChangesは、文書の内容の変更を説明します。
});

connection.onDidCloseTextDocument((params) => {
    // A text document got closed in VS Code.
    // params.uri uniquely identifies the document.
    // テキスト文書は、VS Codeで閉じられました。
    // params.uriは、一意で、文書を識別します。
});

次の手順

Next Steps

詳細については、VS Codeの拡張性モデルをご覧ください。これらのトピックを試してください。:

  • vscode APIの参照-VS Codeの言語サービスとともに、深い言語統合について学びます。

一般的な質問

Common Questions

Q:私が、サーバに添付しようとするとき、私は、「ランタイム・プロセスに結合することができません。 (5000ms後にタイムアウト)」を取得します?

A:あなたは、あなたが、デバッガを添付しようとするとき、サーバが、実行されていない場合、 このタイムアウト・エラーを見るでしょう。クライアントは、言語サーバを開始します。 それで、あなたは、実行中のサーバを持つために、クライアントが開始されていることを確認します。

Home Editor Tools Operation TagScript HPSpace

Copyright (C) 2011 Horio Kazuhiko(kukekko) All Rights Reserved.
kukekko@gmail.com
ご連絡の際は、お問い合わせページのURLの明記をお願いします。
「掲載内容は私自身の見解であり、所属する組織を代表するものではありません。」