コード補完はどのように機能しますか?
-
10-07-2019 - |
質問
多くのエディターとIDEにはコード補完機能があります。それらのいくつかは非常に<!> quot; intelligent <!> quot;です。他はそうではありません。もっとインテリジェントなタイプに興味があります。たとえば、a)現在のスコープで使用可能な関数、b)戻り値が有効な関数のみを提供するIDEを見てきました。 (たとえば、<!> quot; 5 + foo [tab] <!> quotの後に、正しい型の整数または変数名に追加できるものを返す関数のみを提供します。)リストの前にある、より頻繁に使用される、または最も長いオプション。
コードを解析する必要があることを理解しています。ただし、通常、現在のコードの編集が無効な場合、構文エラーが発生します。不完全でエラーが含まれている場合、どのように解析しますか?
時間の制約もあります。リストを作成するのに数秒かかる場合、完了は役に立ちません。時々、完了アルゴリズムは数千のクラスを処理します。
これに適したアルゴリズムとデータ構造は何ですか?
解決
UnrealScript言語サービス製品のIntelliSenseエンジンは複雑ですが、ここではできるだけ概要を説明します。 VS2008 SP1のC#言語サービスは、私のパフォーマンス目標です(理由はあります)。まだありませんが、ctrl + spaceまたはユーザーが.
(ドット)を入力するのを待たずに、単一の文字を入力した後に安全に提案を提供できるほど十分に高速/正確です。 [言語サービスに携わっている]人々がこの主題についてより多くの情報を取得すればするほど、彼らの製品を使用した場合のエンドユーザーエクスペリエンスが向上します。作業の不幸な経験をした多くの製品がありますが、それらは細部にあまり注意を払わず、その結果、コーディングよりもIDEと戦っていました。
私の言語サービスでは、次のようにレイアウトされています:
- カーソルで式を取得します。これは、 member access expression の先頭から、カーソルが置かれている識別子の末尾まで歩きます。メンバーアクセス式は通常
aa.bb.cc
の形式ですが、aa.bb(3+2).cc
のようにメソッド呼び出しを含めることもできます。 - カーソルを囲むコンテキストを取得します。これは非常に注意が必要です。なぜなら、コンパイラーと同じルールに常に従うとは限らないからです(長い話)。通常、これは、カーソルが含まれるメソッド/クラスに関するキャッシュ情報を取得することを意味します。
- コンテキストオブジェクトが
IDeclarationProvider
を実装するとします。ここでGetDeclarations()
を呼び出して、スコープに表示されるすべてのアイテムのIEnumerable<IDeclaration>
を取得できます。私の場合、このリストには、ローカル/パラメーター(メソッドの場合)、メンバー(フィールドとメソッド、インスタンスメソッドの場合を除き静的のみ、ベース型のプライベートメンバーはありません)、グローバル(言語Iの型と定数「取り組んでいます」、およびキーワード。このリストには、aa
という名前のアイテムがあります。 #1の式を評価する最初のステップとして、IDeclaration
という名前のコンテキスト列挙から項目を選択し、次のステップの->
を提供します。 - 次に、
declaration.GetMembers(".")
を表すcc
に演算子を適用して、<!> quot; members <!> quot;を含む別のGetMembers
を取得します。 (何らかの意味で)List<IDeclaration>
。List<Name>
演算子はName
演算子とは異なるため、<=>を呼び出し、<=>オブジェクトがリストされた演算子を正しく適用することを期待しています。 - これは、宣言リストに<=>という名前のオブジェクトが含まれている場合と含まれていない場合があるをヒットするまで続きます。ご承知のとおり、複数の項目が<=>で始まる場合は、それらも表示されるはずです。これを解決するには、最終的な列挙を取得して文書化されたアルゴリズムに渡します。ユーザーに可能な限り最も役立つ情報を提供します。
IntelliSenseバックエンドに関する追加の注意事項を次に示します。
- <=>の実装では、LINQの遅延評価メカニズムを広範囲に使用します。キャッシュ内の各オブジェクトは、そのメンバーに評価するファンクターを提供できるため、ツリーで複雑なアクションを実行するのは簡単です。
- 各オブジェクトがそのメンバーの<=>を保持する代わりに、<=>を保持します。ここで、<=>は、メンバーを説明する特別にフォーマットされた文字列のハッシュを含む構造体です。名前をオブジェクトにマップする巨大なキャッシュがあります。このようにして、ファイルを再解析するときに、ファイルで宣言されたすべてのアイテムをキャッシュから削除し、更新されたメンバーを再入力できます。ファンクターの構成方法により、すべての式はすぐに新しいアイテムに評価されます。
IntelliSense <!> quot; frontend <!> quot;
ユーザーが入力すると、ファイルは正しいよりも構文的に間違っていることが多い。そのため、ユーザーが入力するときにキャッシュのセクションを無計画に削除したくありません。増分更新を可能な限り迅速に処理するために、多数の特殊なケースのルールを用意しています。増分キャッシュは、開かれた場所に対してのみローカルに保持されますファイルを作成することで、ユーザーが入力によってバックエンドキャッシュがファイル内の各メソッドなどの不正な行/列情報を保持していることに気付かないようにすることができます。
- 償還要因の1つは、私のパーサーが高速であることです。低優先度のバックグラウンドスレッドで自己完結型で動作しながら、150msで20000ラインソースファイルの完全なキャッシュ更新を処理できます。このパーサーが開いているファイルのパスを(構文的に)正常に完了するたびに、ファイルの現在の状態がグローバルキャッシュに移動されます。
- ファイルが構文的に正しくない場合、 ANTLRを使用しますフィルターパーサー(リンクについてはごめんなさい-ほとんどの情報はメーリングリストにあるか、ソースを読んで集めたものです)を探してファイルを再解析します:
- 変数/フィールド宣言。
- クラス/構造体定義の署名。
- メソッド定義の署名。
- ローカルキャッシュでは、クラス/構造体/メソッドの定義はシグネチャで始まり、ブレースのネストレベルが偶数に戻ると終了します。メソッドは、別のメソッド宣言に達した場合も終了できます(ネストメソッドはありません)。
- ローカルキャッシュでは、変数/フィールドは直前の unclosed 要素にリンクされます。これが重要な理由の例については、以下の簡単なコードスニペットを参照してください。
- また、ユーザーが入力するときに、追加/削除された文字範囲をマークするリマップテーブルを保持します。これは以下に使用されます。
- メソッドがファイル内で完全解析の間を移動できる/移動するため、カーソルの正しいコンテキストを識別できることを確認します。
- 「宣言/定義/参照に移動」を選択すると、開いているファイル内のアイテムが正しく検索されます。
前のセクションのコードスニペット:
class A
{
int x; // linked to A
void foo() // linked to A
{
int local; // linked to foo()
// foo() ends here because bar() is starting
void bar() // linked to A
{
int local2; // linked to bar()
}
int y; // linked again to A
このレイアウトで実装したIntelliSense機能のリストを追加すると思いました。 それぞれの写真はこちらにあります。
- オートコンプリート
- ツールのヒント
- メソッドのヒント
- クラスビュー
- コード定義ウィンドウ
- ブラウザを呼び出す(VS 2010は最終的にこれをC#に追加します)
- 意味的に正しいすべての参照を検索
他のヒント
特定の実装で使用されているアルゴリズムを正確に言うことはできませんが、経験に基づいた推測を行うことはできます。 トライは、この問題に非常に役立つデータ構造です。IDEはメモリ内に大きなトライを維持できますプロジェクト内のすべてのシンボルと、各ノードに追加のメタデータを追加します。
文字を入力すると、トライのパスをたどります。特定のトライノードの子孫はすべて、補完候補です。 IDEは、現在のコンテキストで意味のあるものでそれらをフィルタリングする必要がありますが、タブ補完ポップアップウィンドウに表示できる数だけ計算する必要があります。
より高度なタブ補完には、より複雑なトライが必要です。たとえば、 Visual Assist X には、キャメルケース記号の大文字のみを入力する必要がある機能があります。たとえば、 、SFNと入力すると、タブ補完ウィンドウに記号SomeFunctionName
が表示されます。
トライ(または他のデータ構造)を計算するには、すべてのコードを解析してプロジェクト内のすべてのシンボルのリストを取得する必要があります。 Visual StudioはこれをIntelliSenseデータベースに保存します。これはプロジェクトと一緒に保存される.ncb
ファイルであるため、プロジェクトを閉じて再度開くたびにすべてを再解析する必要はありません。大規模なプロジェクト(たとえば、ソースコントロールを同期したばかりのプロジェクト)を初めて開いたとき、VSはすべてを解析してデータベースを生成するのに時間がかかります。
増分変更の処理方法がわかりません。あなたが言ったように、コードを書いているとき、それは90%の時間の無効な構文であり、アイドル状態のときにすべてを再解析すると、特にヘッダーファイルを変更している場合、CPUに大きな負担がかかります多数のソースファイル。
(a)実際にプロジェクトをビルドするたびに(または場合によっては閉じる/開くときに)再解析するか、または(b)自分の周りのコードを解析するだけのローカル解析を行うと思われます関連するシンボルの名前を取得するために、いくつかの限られた方法で編集しました。 C ++には非常に複雑な文法があるため、重いテンプレートメタプログラミングなどを使用している場合、暗いコーナーでは奇妙に動作する可能性があります。
次のリンクはさらに役立ちます。.
構文の強調表示:構文の強調表示のための高速の色付きテキストボックス