Miałem już trochę dość ostatnio pisania żmudnego kodu, więc stwierdziłem, że się trochę wspomogę. Skoro mam R#, to czemu nie wykorzystać jego mechanizmu do tworzenia snippetsów?

Problem był banalnie prosty, jednak rozwiązanie rozwinęło się na ponad godzinną pracę a wszystko przez ślepotę ludzką, która chyba boli bardziej niż głupota. No ale do rzeczy :)

Chciałem utworzyć szablony dla Dependency Properties (DP), tak by móc tworzyć ich trzy wersje, które osobiście najczęściej wykorzystuje (o wersjach później). Jednak problem pojawił się kiedy chciałem wykorzystać ownera (właściciela) danej własności przy określaniu PropertyMetadata – czyli przeważnie klasę w której DP jest deklarowany. Myślałem, że takiego macra R# nie posiada więc usiadłem i go napisałem :/ I kiedy go nazwałem okazało się iż macro istnieje i nazywa się Containing Type Name. Jednakże przeszedłem przez proces debuggowania i testowania rozszerzenia, więc chociaż trochę o nim napiszę nim przejdę do szablonów :)

Macro

Blog Hadi Haririego zawiera bardzo dużo informacji jak tworzyć szablony i jak rozszerzać system o kolejne macra (link do ostatniego opublikowanego postu zawierającego spis treści). Polecam wszystkim zainteresowanym tematem zapoznać się z wpisami, naprawdę pomagają :)

W skrócie, by utworzyć nowe makro, które będzie robiło dosłownie do samo co Containing Type Name należy utworzyć projekt Class Library w VS z target na .NET 4.0 (nie sprawdzałem na innych wersjach .NET) a następnie dodać referencje do (resztę można usunąć z projektu):

  • JetBrains.Platform.ReSharper.ProjectModel.dll
  • JetBrains.Platform.ReSharper.Shell.dll
  • JetBrains.Platform.ReSharper.TextControl.dll
  • JetBrains.Platform.ReSharper.Util.dll
  • JetBrains.ReSharper.Feature.Services.dll
  • JetBrains.ReSharper.Psi.dll
  • JetBrains.ReSharper.Psi.Services.dll

Wszystkie pliki DLL znajdują się w katalogu: X:Program Files[ (x86)]JetBrainsReSharperv5.1Bin gdzie X to litera dysku twardego, a [] opcjonalny tekst w zależności od systemu (64/32 bit).

Teraz wystarczy, że stworzymy klasę i zaimplementujemy interfejs IMacro. Znaczenie poszczególnych metod interfejsu znajdziecie na blogu Hadiego – sorki, ale osobiście wolę w oryginale niż tłumaczyć na polski.

Nasza klasa powinna wyglądać tak:

using System.Collections.Generic;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Hotspots;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Macros;
using JetBrains.ReSharper.Psi.Services;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Util;

namespace LineByLine.ReSharper.LiveTemplates
{
    [Macro("LineByLine.ReSharper.LiveTemplates.ContainingClassName",
        ShortDescription = "Containing class name",
        LongDescription = "Returns the name of the class in which specific macro is used.")]
    public class ContainingClassName: IMacro
    {
        public string GetPlaceholder()
        {
            return "a";
        }

        public bool HandleExpansion(IHotspotContext context, IList<string> arguments)
        {
            return false;
        }

        public HotspotItems GetLookupItems(IHotspotContext context, IList<string> arguments)
        {
            var type =
                TextControlToPsi.GetElement<ITypeDeclaration>(
                    context.SessionContext.Solution, context.SessionContext.TextControl);
            
            return type == null ? null : MacroUtil.SimpleEvaluateResult(type.DeclaredName);
        }

        public string EvaluateQuickResult(IHotspotContext context, IList<string> arguments)
        {
            return null;
        }

        public ParameterInfo[] Parameters
        {
            get { return EmptyArray<ParameterInfo>.Instance; }
        }
    }
}

Jeżeli teraz chcemy sprawdzić funkcjonalność naszego macra, to polecam w zakładce debug właściwości projektu ustawić start external program na devenv.exe i dodać parametry uruchomienia aplikacji:

/ReSharper.Plugin NAZWA_DLL_NASZEGO_ASSEMBLY

Settings

Następnie po uruchomieniu debug i otworzeniu VS, należy utworzyć nowy szablon z wykorzystaniem naszego macro, stworzyć testowy projekt i przetestować macro :) powinno zadziałać :)

Ja właśnie w trakcie tworzenia nowego testowego macra odkryłem Containing type name, więc trochę się wkurzyłem, ale co tam :)

Szablony

Skoro odkryłem, że moje makro istnieje, nie było sensu go używać, użyłem więc wbudowanego, dzięki czemu szablony są dostępne bez zbędnego instalowania dllki w pluginach ReSharpera.

Trzy LiveTemplates o których ciągle mowa to:

  • dp – domyślne DP, bez metadanych;
  • dpp – domyślne DP rozszerzone o informację o domyślnej wartości danej własności (podstawowy konstruktor PropertyMetadata);
  • dpo – dpp, rozszerzone o PropertyChangedCallback w najbardziej rozbudowanych konstruktorze PropertyMedatada.

Przykłady już utworzonych DP za pomocą LiveTemplates:

// dp LiveTemplate
public static readonly DependencyProperty TestBoolDpProperty = DependencyProperty.Register(
    "TestBoolDp",
    typeof(bool),
    typeof(MainPage),
    null);
 
public bool TestBoolDp
{
    get { return (bool)GetValue(TestBoolDpProperty); }
    set { SetValue(TestBoolDpProperty, value); }
}

// dpp LiveTemplate
public static readonly DependencyProperty TestBoolDppProperty = DependencyProperty.Register(
    "TestBoolDpp",
    typeof(bool),
    typeof(MainPage),
    new PropertyMetadata(default(bool)));
 
public bool TestBoolDpp
{
    get { return (bool)GetValue(TestBoolDppProperty); }
    set { SetValue(TestBoolDppProperty, value); }
}

// dpo LiveTemplate
public static readonly DependencyProperty TestBoolDpoProperty = DependencyProperty.Register(
    "TestBoolDpo",
    typeof(bool),
    typeof(MainPage),
    new PropertyMetadata(default(bool), OnTestBoolDpoChanged));
 
public bool TestBoolDpo
{
    get { return (bool)GetValue(TestBoolDpoProperty); }
    set { SetValue(TestBoolDpoProperty, value); }
}
 
private static void OnTestBoolDpoChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs arg)
{
    MainPage mainPageDo = depObj as MainPage;
            
} 

Sposób instalacji rozwiązania jest dość prosty, wystarczy pobrać z stąd szablony w pliku XML, i następnie w oknie przeglądania szablonów (ReSharper | Live Templates) kliknąć na przycisk Import i wybrać ściągnięty plik XML (polecam import nad kopiowaniem treści szablonów ze względu na zachowanie ustawień makr).

Import

Sposób wykorzystania jest taki sam jak z każdym snippetem, pierwsze literki i TAB lub ENTER :)

Są jednak dwie ważne sprawy, po pierwsze szablony są wyłącznie dla C#, po drugie R# nie umożliwia nam określenie language specific dla Silverlight, więc DP są dostępne wszędzie, zaś będą działać tylko i wyłącznie w projekcie SL, ze względu na to, że w WPF trochę inaczej rejestruje się DP. Dodatkowo jeżeli więc macie już snippety o takich nazwach, może nastąpić konflikt.

Krótki opis szablonów, przykłady oraz sposób instalacji jak i bezpośrednio szablony dostępne są na stronach Code Gallery.