例 -言語サーバー
言語サーバは、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を押すことで、拡張をデバッグします。 拡張を立ち上げて、デバッグすることについての詳しい説明は、あなたの拡張を実行し、デバッグするを参照してください。
サーバは、拡張(クライアント)内で、実行されている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の問題の結果を最大に設定を変更します。:
追加された言語機能を追加する
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[] => {
// 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 => {
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:あなたは、あなたが、デバッガを添付しようとするとき、サーバが、実行されていない場合、 このタイムアウト・エラーを見るでしょう。クライアントは、言語サーバを開始します。 それで、あなたは、実行中のサーバを持つために、クライアントが開始されていることを確認します。