ReSharper.QuickFix
. I am writing additional options for this menu. Why? Because it sometimes saves time. Let's take a look at how to write context action for Resharper.Plugins
folder in the resharper bin
folder. For debugging, there is no need to copy them there - you can simply specify the name and the path to the plugin as the arguments of the studio itself ( devenv.exe
). The syntax is something like this:devenv.exe /ReSharper.Plugin c: \ path \ to \ your.dll
ReSharper
appears, because I have no idea what might be needed.CSharpContextActionBase
(in the case of C #), as well as implementing several other interfaces. Fortunately, part of the "plumbing" implemented the developers of other plug-ins. For context ContextActionBase
, I add the ContextActionBase
class to my project, which was written by the authors of the Agent Johnson plugin. Actually the file itself can be found here .ContextActionBase
GetText()
method. This method returns in the string what will be written for your command in the CA drop-down menu.IsAvailable(IElement)
method. Determines whether your CA is applicable at a given code point or not. IElement
is your link to the code point where the cursor is. From this point of the code, you can bypass even the entire tree of the file.Execute(IElement)
method. If the user has clicked on your SA, then you can apply it. We again have a link to IElement
, i.e. we can walk along the code and choose what to change and where.Math.Pow()
calls with integer values. This is necessary becauseMath.Pow(x, 2.0)
← it is bad and slowx*x
← much fastertext
field has been added so that we can directly prompt the user in the CA menu what will happen to his code after refactoring.
[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)
{
//
}
⋮
}
Math.Pow()
and this body has an integer degree - for example, 3.0. How to do it? First we find the place where the user has the cursor. Then, we get those nodes of the syntax tree, which stand in the same place as the cursor, and try to bring them to the expected types. Since Math.Pow()
is a function call, we expect to see a IInvocationExpression
with IInvocationExpression
in its body. And so on, in a chain, and we always use the as
operator in case that the expression is not what we expect.true
. In all other cases, return false
.
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 ;
}
text
variable before returning true
? This is to make the CA better read by the user. Yes, and as for ReadLockCookie
in which the code is wrapped - this is an element of Resharper's internal semantics. I have no idea what he is doing - just copying him from the examples so, just in case. After all, there is no detailed, updated documentation on writing plugins for Resharper.true
from IsAvailable()
, Resharper will want to know which text to draw in the menu. In this case, we already know what to return - the content of the text
variable.
protected override string GetText()
{
return text;
}
Execute()
method is called. And here our replacement algorithm is just starting to work. Remember - we want to change, say, Math.Pow(x, 3.0)
to x*x*x
. How to do it?Math.Pow()
. We pull out both parameters (in the example above, x
and 3), gently converting the values even if it is written, for example, not 3.0 but 3.0f. Next, we determine how long the expression is to the left - because if we raise to the power of x
, then we can write x*x*x
, but if x+y
we will have to write with brackets (x+y)*(x+y)*(x+y)
To do this, we interrupt the type, and if it is ILiteralExpression
or IReferenceExpression
then hooray is the expression “short”.StringBuilder
to make a string for replacement. But then interesting things happen.ICSharpExpression
, which allows us to create a node from our string that can replace the Math.Pow
node. The following expression does just that - with LowLevelModificationUtil
we replace one node with another.
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() });
}
}
}
}
ContextActionBase
is here . This example was tested on version 4.5 of Resharper, I don’t know anything about version 5 :)null
checks and so on. Good luck! ■ Source: https://habr.com/ru/post/74026/
All Articles