そのため、コンテキストアクションは、左側に矢印付きで表示されるメニューであり、コード内の「迅速な修正」の可能性を提供します。 ところで、このメニューの開始をバインドしたい場合、コマンドは
ReSharper.QuickFix
と呼ばれます。 このメニューの追加オプションを書いています。 なんで? 時間が節約できることがあるからです。 Resharperのコンテキストアクションの作成方法を見てみましょう。
Resharper拡張機能は、リゾルバーの
bin
フォルダーの
Plugins
フォルダーに配置される通常のクラスライブラリ(DLL)です。 デバッグのために、それらをコピーする必要はありません。スタジオ自体(
devenv.exe
)を呼び出すための引数としてプラグインの名前とパスを指定するだけです。 構文は次のようなものです。
devenv.exe /ReSharper.Plugin c:\ path \ to \ your.dll
何も書かずにF5に従ってデバッグを開始すると、プラグインはリゾルバーのプラグインのリストに既に表示されます。 もちろん、今からやろうとするよりも、何らかの種類のコンテンツを追加する方が良いでしょう。
最初に行うことは、必要なリゾルバーアセンブリへのリンクを追加することです。 名前
ReSharper
が表示されるすべてのアセンブリへのリンクを愚かに追加します。 何が必要かわからない。
コンテキストアクションは、
CSharpContextActionBase
(C#の場合)から継承することによって、および他のいくつかのインターフェイスを実装することによって実行されます。 幸いなことに、配管の一部は他のプラグインの開発者によって実装されました。 コンテキスト
ContextActionBase
場合、 Agent Johnsonプラグインの作成者が作成した
ContextActionBase
クラスを
ContextActionBase
に追加します。 実際には、ファイル自体はここにあります 。
ここで、CAを作成するには、2つのことを行う必要があります。
-
ContextActionBase
からCAを継承する
- 結果のクラスをContextActionAttribute属性で装飾します
動作させるには、結果のクラスに4つのメソッドのみを追加する必要があります。
- デフォルトのコンストラクタ。 基本的にここで行うことはありません。
-
GetText()
メソッド。 このメソッドは、CAドロップダウンメニューのコマンドに対して記述される文字列を返します。
-
IsAvailable(IElement)
メソッドIsAvailable(IElement)
CAがコードの特定のポイントで適用可能かどうかを決定します。IElement
は、カーソルが置かれているコード内のポイントへのリンクです。 コードのこの時点から、少なくともファイルツリー全体をバイパスできます。
-
Execute(IElement)
メソッド ユーザーがCAをクリックした場合、それを適用できます。 再びIElement
へのリンクがありIElement
。 コードを歩き回り、何をどこで変更するかを選択できます。
簡単な例を見てみましょう。
Math.Pow()
呼び出しを整数値ですばやく
Math.Pow()
化する関数を実装する必要があると想像してください。 これは必要です
-
Math.Pow(x, 2.0)
←悪くて遅い
-
x*x
←はるかに高速
それで、このミニリファクタリングを徐々に実装してみましょう。
まず、CAのクラスを作成し、メタデータの小さなセットで装飾します。
text
フィールドが追加されるため、CAメニューで直接リファクタリング後のコードに何が起こるかをユーザーに伝えることができます。
[ContextAction(Group = "C#" , Name = "Inline a power function" ,
Description = "Inlines a power statement; eg, changes Math.Pow(x, 3) to x*x*x." ,
Priority = 15)]
internal class InlinePowerAction : ContextActionBase
{
private string text;
public InlinePowerAction(ICSharpContextActionDataProvider provider) : base (provider)
{
//
}
⋮
}
フレームの準備ができました。 次に、CAが適用可能かどうかを判断する方法を学習する必要があります。
CAは、
Math.Pow()
本体に座っており、この本体が整数度(たとえば3.0
Math.Pow()
である場合にのみ適用できます。 どうやってやるの? まず、ユーザーがカーソルを持っている場所を見つけます。 次に、カーソルと同じ場所にある構文ツリーのノードを取得し、予想されるタイプにキャストしようとします。
Math.Pow()
は関数呼び出しであるため、本体に
IInvocationExpression
を
IInvocationExpression
が表示されることを期待しています。 など、チェーンに沿って、そして式が期待したものでない場合に備えて、
as
演算子を常に使用します。
チェーン全体の最後で、指数の値を見つけて確認します。 整数であり、1〜10-CAが適用可能な場合、
true
返し
true
。 それ以外の場合はすべて、
false
返し
false
。
以下にコード例を示します。 読むのではなく、デバッガーを使って歩いてみる方が良いでしょう。 これは、ReSharper全体での作業に適用されます-構文ツリーの構造について詳しく知る最良の方法は、デバッガーを使用することです。
protected override bool IsAvailable(JetBrains.ReSharper.Psi.Tree.IElement element)
{
using (ReadLockCookie.Create())
{
IInvocationExpression invEx = GetSelectedElement<IInvocationExpression>( false );
if (invEx != null && invEx.InvokedExpression.GetText() == "Math.Pow" )
{
IArgumentListNode node = invEx.ToTreeNode().ArgumentList;
if (node != null && node.Arguments.Count == 2)
{
ILiteralExpression value = node.Arguments[1].Value as ILiteralExpression;
if ( value != null )
{
float n;
if ( float .TryParse( value .GetText().Replace( "f" , string .Empty), out n) &&
(n - Math.Floor(n) == 0 && n >= 1 && n <= 10))
{
text = "Replace with " + (n-1) + " multiplications" ;
return true ;
}
}
}
}
}
return false ;
}
true
返す前に変数
text
値を設定する方法を参照してください。 これにより、ユーザーがCAをより適切に読み取ることができます。 はい。コードがラップされている
ReadLockCookie
については、Resharperの内部セマンティクスの要素です。 彼が何をしているかわからない-念のために、このような例からコピーするだけ。 結局のところ、Resharperのプラグインの作成に関する詳細で更新されたドキュメントはありません。
IsAvailable()
から
true
を返した
true
、
IsAvailable()
はメニューに描画するテキストを知りたいです。 この場合、何を返すか、つまり
text
変数の内容をすでに知っています。
protected override string GetText()
{
return text;
}
ああ、すべてがとても簡単だったら...
ユーザーには、意図した目的でCAを使用する機会があります。 彼がメニューでそれをクリックすると、
Execute()
メソッドが呼び出されます。 そして、ここで置換アルゴリズムが機能し始めます。 覚えておいてください-たとえば、
Math.Pow(x, 3.0)
を
x*x*x
に変更したいのです。 どうやってやるの?
ここでも、
Math.Pow()
を含むツリーノードが必要です。 両方のパラメーター(上記の例
x
および3)を引き出し、たとえ3.0でなく3.0fのように書き込まれている場合でも、値を慎重に変換します。 次に、式の長さを左に決定します
x
の累乗にすると
x*x*x
書くことができますが、
x+y
場合は角括弧
(x+y)*(x+y)*(x+y)
書く必要があるため
(x+y)*(x+y)*(x+y)
。 これを行うために、型を中断します
ILiteralExpression
または
IReferenceExpression
場合、cheersは式「short」です。
式と整数の力を受け取ったら、
StringBuilder
を使用して置換する文字列を作成します。 しかし、その後、興味深いことが起こります。
最初に、
ICSharpExpression
型のオブジェクトを作成します。これにより、
Math.Pow
ノードを置き換えることができるノードをラインから作成できます。 次の式はそれを実行します
LowLevelModificationUtil
を使用して、あるノードを別のノードに置き換えます。
protected override void Execute(JetBrains.ReSharper.Psi.Tree.IElement element)
{
IInvocationExpression expression = GetSelectedElement<IInvocationExpression>( false );
if (expression != null )
{
IInvocationExpressionNode node = expression.ToTreeNode();
if (node != null )
{
IArgumentListNode args = node.ArgumentList;
int count = ( int ) double .Parse(args.Arguments[1].Value.GetText().Replace( "f" , string .Empty));
bool isShort = node.Arguments[0].Value is ILiteralExpression ||
node.Arguments[0].Value is IReferenceExpression;
var sb = new StringBuilder();
sb.Append( "(" );
for ( int i = 0; i < count; ++i)
{
if (!isShort) sb.Append( "(" );
sb.Append(args.Arguments[0].GetText());
if (!isShort) sb.Append( ")" );
if (i + 1 != count)
sb.Append( "*" );
}
sb.Append( ")" );
// now replace everything
ICSharpExpression newExp = Provider.ElementFactory.CreateExpression(
sb.ToString(), new object [] { });
if (newExp != null )
{
LowLevelModificationUtil.ReplaceChildRange(
expression.ToTreeNode(),
expression.ToTreeNode(),
new [] { newExp.ToTreeNode() });
}
}
}
}
以上です。 すべてが機能します。 完全なCAはここからダウンロードできます 。 基本クラス
ContextActionBase
がここにあることを思い出してください。 この例はResarperバージョン4.5でテストされました。バージョン5については何も知りません:)
例が複雑であることは知っています。 構文ツリーをたどることは簡単な作業ではありません。 私が書いたほぼすべての SAに苦しんでいます。 もちろん、デバッガーはこの点で非常に役立ちますが、複雑なアクションゲームを作成する場合は、たとえば、同じF#で簡単なDSLをスケッチすることをお勧めします。C#でのツリー検索は、これらのすべての型キャスト、
null
チェックなどで乱雑に見えるためです。 頑張って ■