Source templates in C#
Traditionally, templates or snippets are stored and managed outside your source code. This makes sense because normally a template helps you quickly produce some universal boilerplate code.
However, you may want to produce some repeatable code that is only relevant in your current project or solution. JetBrains Fleet allows you to streamline such tasks with Source Templates.
How it works
In contrast to traditional templates, source templates can be created anywhere in the code of your project as extension methods. You can define them for some specific types of your project or for any standard types. You can even make a source template available for all types by creating it as an extension method for object
.
As soon as a template is defined, it becomes available in the code completion list for the objects of corresponding type and its inheritors. When you choose the template in the list, JetBrains Fleet inserts the code from the body of the template method into your code.
Here is an example that illustrates a simplest application of a source template. Our template forEach
, will be available for all generic collections — this is defined by the parameter this IEnumerable<T> x
. It will insert code that iterates the collection, as specified in the method body. We can define the template in any static class in our project. JetBrains Fleet will identify it as a template by the [SourceTemplate]
attribute:
To deploy this template, we now can use automatic completion for any collection object:
When you select the template item, the object is replaced with the template text:
Note that the //$ $END$
comment in the template definition is nothing else but the predefined template parameter defining the caret position after the template is applied. You can use other parameters in your template to make the template more flexible.
Why use source templates
As mentioned above, source templates are most helpful for the code blocks that you want to reuse in the current project or solution. Here are some points that show the advantages of source templates over traditional templates:
To work with source templates, you do not have to switch anywhere — everything is in your editor.
The templates are strongly typed, which means that you can only call them on relevant objects.
As long as template definitions compile, you can be sure that the template code has no errors.
While creating and editing source templates, your favorite JetBrains Fleet features are at your disposal: code inspection, navigation features, code completion, just to name a few.
Create source templates
For definitions of your source templates, you can create a new class or use an existing static class where you keep your extension methods.
Template method and its body
A source template must be a public extension method and have the [SourceTemplate]
attribute. The attribute is defined in the JetBrains.Annotations
namespace, together with [NotNull]
, [CanBeNull]
and other code annotation attributes. Therefore, to define source template methods, you should enable code annotations in your project.
In the template body, you can write the code that does anything you like. Normally, it does something with the caller object, but it is not necessary.
You may also need to use some code that would not compile in the template method. For example, you may want to use the caller object name to generate a name of a local variable. In this case, place this code inside a line or a block comment that starts with the '$' sign:
Target expression
When a source template expands on a complex expression, it captures only its latest part by default. If you need to capture the entire expression, you have to specify this explicitly using the Target
property with the SourceTemplateTargetExpression.Outer
value. In the example below, the Sqrt
template will be expanded over the entire x+y
expression resulting in var sqrt = Math.Sqrt(x+y)
Parameters and macros
In source templates, you can use parameters and macros. Depending on the macro that you are going to use for the parameter, you can choose between several ways of specifying and using parameters.
You can create a template parameter by adding a new parameter to the template method. By default, it will behave as an editable parameter, that is it will receive focus during the hot spot session when you apply the template. If you want to define a macro for this parameter, you need to add the
[Macro]
attribute as shown in the example below.The
Expression
property of the attribute defines which macro should be used. You can specify one of the available template macros.The
Editable
property optionally specifies whether the user will be able to edit the parameter when the template is applied. By default, all user-defined parameters are editable; the value-1
makes the parameter non-editable.If the same parameter is used several times in the template, only one occurrence becomes editable when the template is applied; other occurrences are changed synchronously. If necessary, you can define which occurrence becomes editable by specifying its zero-based index in the 'Editable' property.
[SourceTemplate] public static void newGuid(this object obj, [Macro(Expression = "guid()", Editable = -1)] string newguid) { Console.WriteLine(newguid); }You can turn any local variable in your template method into a template parameter. To do so, you need to add the
[Macro]
attribute to the template method definition, and specify the variable name in itsTarget
property. For example:[SourceTemplate] [Macro(Target = "item", Expression = "suggestVariableName()")] public static void forEach<T>(this IEnumerable<T> collection) { foreach (var item in collection) { //$ $END$ } }You can use predefined parameters as well as all user-defined parameters the same way as in other JetBrains Fleet templates, wrapping the parameter identifier with two
$
signs$param_name$
.You can use user-defined template parameters inside string literals, e.g:
Console.WriteLine("A random GUID: $newguid$");To use template parameters outside string literals, you need to put them in a special template comment that starts with the
$
sign://$ $param_name$
. or/*$ $param_name$ */
. For example:[SourceTemplate] [Macro(Target = "newguid", Expression = "guid()", Editable = -1)] public static void newGuid(this object obj) { //$ var guid = "$newguid$"; Console.WriteLine("A random GUID: $newguid$"); }The caller object as well as template parameters created as method parameters and local variables can be used both with and without the
$
signs inside the special templates comments.
Apply source templates
To apply your source template, first make sure that the template is in the scope. That is, either you are in the same namespace or that the template's namespace is explicitly imported.
Create a code fragment from a source template
Place the caret where you want to deploy the template.
Type an object, for which you want to deploy the template, then type a dot, and then start typing the name of the template or its CamelHumps abbreviation.
Select the template in the completion list and click it or press Enter.
The caller object, the dot, and the part of the template name that you typed are replaced with the template body.
If the template has editable parameters (that is, requires user input), JetBrains Fleet deploys a hot spot session in the editor and sets the input position at the first parameter. Then you can do the following:
If JetBrains Fleet suggests some values for the current parameter, use Up and Down arrow keys to navigate through the list of suggested values, or just type in a desired value.
Press Tab or Enter to accept the value and move to the input position of the next parameter. If this is the last parameter, the hot spot session completes and the caret moves to the end position defined for the session.
Press Shift+Tab to move the input focus to the input position of the previous parameter.
Press Esc to exit the hot spot session. In this case, all session parameters will be initialized with default values.