Templates Galore: Extending Functionality with Macros

This is final part of a four part series on Templates:

 

In previous posts we saw how to create Live, File and Surround templates. We also saw that the Template Editor provided us with the ability to apply macros to placeholders. For instance, in the first post we saw how to set the name of a variable to that of another existing one, with the first letter in lowercase, simply by choosing a macro and applying it to the placeholder.

Despite  ReSharper shipping with quite a few macros out of the box, there are always instances when we want something specific which is not available. In this post we’ll see how to easily solve this problem by writing a custom macro.

Creating a custom Macro

The process for creating a custom macro is overall quite simple:

  1. Create a class that implements IMacro
  2. Decorate it with the Macro attribute
  3. Install it as a ReSharper plug-in

 

1. Implementing IMacro

We are going to create a simple macro that merely returns the type of the method it is used in. This not only shows us the basics of creating macros, but helps solve someone’s problem. Let’s start by creating an class library project and adding a class named MethodResultTypeMacro.

image

The class should implement the IMacro interface. This is declared in the JetBrains.ReSharper.Features.Services.dll assembly (I’ve posted the complete project to GitHub so you can see all required references).

public class MethodResultTypeMacro: IMacro
{
    public string GetPlaceholder()
    {
        throw new NotImplementedException();
    }

    public ParameterInfo[] Parameters
    {
        get { throw new NotImplementedException(); }
    }

    public string EvaluateQuickResult(IHotspotContext context, IList arguments)
    {
        throw new NotImplementedException();
    }

    public HotspotItems GetLookupItems(IHotspotContext context, IList arguments)
    {
        throw new NotImplementedException();
    }

    public bool HandleExpansion(IHotspotContext context, IList arguments)
    {
        throw new NotImplementedException();
    }
}

Depending on what our Macro needs to do, the implementation details of these methods vary.

  • GetPlaceHolder returns a string that defines the value of the placeholder. It is mostly used for formatting, and if there is no default value, you can return simply a string such as “a”, which is what the vast majority of OOB macros do.
  • Parameters define the parameters that our macro can have. Macros that have parameters include the one we used in the first post (Value of another variable with the first character in lower case). Macros with parameters have these in bold in the name and description. If our macro does not have parameters, this should return EmptyArray<ParameterInfo>.Instance

          SNAGHTML299c63fa

          image

  • GetLookupItems returns a list of items that will be displayed when a placeholder has focus. For instance, when invoking a Live Template that has a placeholder for a type, this would contain the list of valid types. It returns HotSpot items. A hotspot refers to elements in the template that are placeholders, whether they are editable or not.
  • EvaluateQuickResult is called every time a hotspot obtains a value. Often the value of one hotspot can influence that of another. For instance, assume a a macro that provides information on MVC Controllers. Based on the selected controller, the next hotspot which could provide information on Actions would vary. By implementing this method we can update this information. If no additional processing is required, null is returned.
  • HandleExpansion is used to perform extra activities on a hotspot. For instance when navigating to a different hotspot. If no additional processing is required, we return false.

Based on the Stackoverflow post mentioned previously, the author wants to be able to create a Live Template that allows him to easily insert a contract in a method that ensures that the return value is not null (this is part of Code Contracts):

In order to do this, we would define the following Live Template:

image

The placeholder we have is the $RETURNTYPE$, which we want to be filled in automatically with the return type of the method where this is declared. As such, what we need to do is implement the GetLookupItems to provide the return type of the method. Below is the corresponding code:

public HotspotItems GetLookupItems(IHotspotContext context, IList<string> arguments)
{
    var method = TextControlToPsi.GetContainingTypeOrTypeMember(context.SessionContext.Solution, context.SessionContext.TextControl);

    if (method is IMethod)
    {
        var lookupItems = new List<ILookupItem>();

        var item = new TextLookupItem(((IMethod)method).ReturnType.GetPresentableName(method.Language));
        
        lookupItems.Add(item);
        
        var hotSpotItems = new HotspotItems(lookupItems);
        
        return hotSpotItems;
    }
    return null;
}

 

What we are doing is using the TextControlToPsi class to provide us with information about a possible method the current hotspot is used in. Once we obtain this value, if it is in fact a method, we will then obtain the result type via the ReturnType property. This will return the fully qualified type name. We can obtain a shorter presentation using the GetPresentableName method. We then need to return this as the result of the GetLoookupItems call. This returns HotSpotItems which contains a list of ILookupItem. A specific implementation of this interface is the TextLookupItem which is for regular text values. We thus create a new instance of this class, add it to the list and return this last one.

Since we do not need any additional functionality, the rest of the methods return the default values.

 

2. Metadata for our Macro

We now need to provide some additional information for our Macro so that ReSharper knows what information to display in the Macro selection box. This is provided with the Macro attribute and has three properties:

[Macro("CustomTemplateMacros.MethodResultTypeMacro",
    ShortDescription = "Result type of containing method",
    LongDescription = "Obtains the result type of the containing method it is used in")]

 

  • Name normally refers to the package and macro name. Macros are referenced by this field.
  • ShortDescription provides the description that will be displayed in Macro selection box.
  • LongDescription provides the description that will be displayed in the description box when selecting the macro.

If the Macro has properties, these can be referenced (and hyperlinks provided to them so that they are clickable) using the syntax {#0:description of variable} where the number indicates the position of the variable. For instance, in the case of replacing one variable with the value of another in lowercase, the ShortDescription would be:

Value of {#0:another variable} with the first character in upper case

 

3. Installing the Macro

We now compile the project and place it in a folder un the ReSharper BinPlugins folder [See my posts on ReSharper plug-ins for more information]. Once we restart Visual Studio, we should now see the Macro displayed in the Macro selection box. Below is what our Live Template definition looks like. Notice that we’ve made the occurrence of the placeholder non-editable since we are not interested in choosing an option.

SNAGHTML2a1cd342

SNAGHTML2a18fc16 

Now all we need to do is type crn and hit Tab to have a contract inserted automatically with the corresponding return type of the containing method:

public ActionResult Index()
{

    Contract.Ensures(Contract.Result<ActionResult>() != null);

 

Summary

Hopefully you’ve now seen that creating custom macros is not rocket science in ReSharper and opens up a world of possibilities when dealing with Live Templates, allowing us to do pretty much anything. The entire code for the project has been uploaded to GitHub under my Blog repository. Please feel free to use it as you wish.

This entry was posted in News and Events and tagged , . Bookmark the permalink.

13 Responses to Templates Galore: Extending Functionality with Macros

  1. Chris Martin says:

    Awesome. Thanks!

  2. Aleksey says:

    Hello,

    Is it possible to create a macro which would add several things in different places in a file?
    E.g. based on the provided “ParameterName” the macro would add:

    1) An intialization statement inside the constructor
    2) A constant for the parameter name
    3) A C# property retrieveing the parameter from a collection of parameters

    Thanks!

  3. Hadi Hariri says:

    @Aleksey,

    Out of the box, it’s not really that straight-forward to have the macro create more than one file, i.e. not supported. However, I’m guessing you could hack it somehow since ultimately you can do pretty much whatever you want from the code.

  4. Aleksey says:

    @Handi,

    I mean adding things in several specific (like the constructor) places of an existing file.

  5. Diego says:

    Hi! I just would like to know, that macros created for ReSharper 5.x working in version 6.x?

    Thx

  6. Hadi Hariri says:

    @Diego,

    There are some updates required depending also on what exactly your macro is doing.

  7. Diego says:

    Hi,

    thats what my macro does:

    public HotspotItems GetLookupItems(IHotspotContext context, IList arguments)
    {
    var input = new ContainingTypeMemberNameMacro().GetLookupItems(context, arguments).Items.FirstOrDefault();
    var inputText = input == null ? string.Empty : input.DisplayName.Text;
    string result = string.Empty;
    if (!string.IsNullOrEmpty(inputText))
    {
    foreach (var match in Regex.Matches(inputText, “[A-Z][^A-Z]*”))
    result += (string.IsNullOrEmpty(result) ? string.Empty : “_”) + match.ToString().ToLower();
    }
    return new HotspotItems(new List { new TextLookupItem(result) });
    }

    What modifications should I do?

  8. Hadi Hariri says:

    @Diego,

    What’s not compiling? Can you please post this on our forums? It’s better to provide support via that.

  9. Diego says:

    Thx, but I already figured out what was the problem. As the error message sad, I needed to update the references from 5.x to 6.x in my macro project. :)

  10. Roberto says:

    Hi,

    I’m creating a macro for using it into a File Template. How could I access the file name? I don’t know how to proceed and I can’t find any proper example of this so any help would be really appreciated.

    Best regards,
    Roberto.

  11. citizenmatt says:

    The devguide provides a good overview of how to create a Live Templates macro. Also, it’s useful to point dotPeek at ReSharper’s bin directory and look at the other implementations of IMacroDefinition and IMacroImplementation. For example, the FileNameMacroImpl class is ReSharper’s own implementation of the file name macro, and this shows how to get the document from the IHotspotContext, and the file name from the document.

  12. sam holder says:

    Hey. Is it possible to create a macro which gets the Original filename of the first file in a multi file template?

    Thanks in advance!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">