Pro své potřeby jsem vytvořil 4 různá makra:
Makro vloží do textu řetězec ''' <summary></summary>. Shledáte ho užitečným ve všech případech, kdy se vám nelíbí vestavěná funkčnost Visual Studia, které vkládá po napsání třech apostrofů do dokumentu na můj vkus příliš místa zabírající XML - <summary> na tři řádky a <remarks> k tomu. Pro prvek výčtového typu opravdu trochu příliš!
I vtakto "malém" kalru se ale najde jedna zapeklitost - pokud totiž použijete objekt typu EnvDTE.TextSelection a nastavíte jeho vlastnost Text na "''' <summary></summary>" dostaví se stejný efek, jako kdyby ste postupně ťukali do kláves zadávajících příslušné znaky. Tedy do dokumentu se vloží "dlouhý" XML komentář a k němu ještě váš <summary>. Selection.Text, tedy použít nelze. Je nutno použít EnvDTE.EditPoint, který získáte jednoduše jako selection.ActivePoint.CreateEditPoint. Ten má metodu Insert a ta už funguje z tohoto pohledu korektně.
Za účelem zvýšení uživatelské přívětivosti makro přesune kurzor doprostřed tagu <summary> a pokud byl vybrán nějaký text, vloží jej tam.
Předchozí ukázka byla opravdu jednoduchá a textově postavená. 23 řádků ;-). Tohle již je zapeklitější. Makro vytvoří k prvku, který začíná na řádku, kde se nachází textový kurzor, plnohodnotný XML komentář výrazně podobný tomu, který VS vloží po zadání '''. Ale moje osobní preference je mít <summary> jen na jeden řádek a <remarks automaticky nevkládat. Pro vlastnosti, které jsou read-only nechci <value> a pro ty, které jsou write-only nechci <returns>. Tedy staň se!
Jistě vás napadá zajímavá otázka: Jak zjistit co je to za prvek pod kurzorem, jaké má parametry atp. A napadá vás jistě i odpověď: Syntaktická analýza. To vypadá na pěkný semestrální projekt do předmětu Programovací jazyky a překladače! Tedy, byla by to možnost a jak se později ukáže, mít vlastní syntaktický analyzátor by se velmi hodilo, ale je to spousta práce a času. Naštěstí Visual Studio poskytne některé informace o objektu pod po chvíli přemlouvání samo.
První úlohou je zjistit jaký prvek se pod kurzorem vůbec ukrývá. To se ukázalo jako trošku "tricky" úloha. Visual Studio vám ocotně prozradí uvnitř jakého prvku se kurzor nachází, ale jen v případě, že vy mu řeknete o jaký druh prvku (třída, metoda, vlastnost, ...) máte zájem. To je trošku nepraktické v případě, kdy potřebujete dostat jakýkoliv prvek od úrovně metody výše. Nezbývá tedy, než se Visual Studia zeptat na všechny prvky - typicky se váš kurzor nachází uvnitř metody, metoda je uvnitř třídy, třída uvnitř jmenného prostoru ... - a pak vybrat ten, který začíná na stejném řádku jako je kurzor. Na to jsem hned využil nových vlastností VB9 a implementoval jsem extension metodu:
<ExtensionAttribute()> _
Private Function GetCodeElement(ByVal p As EnvDTE.EditPoint) As EnvDTE.CodeElement
For Each t As vsCMElement In [Enum].GetValues(GetType(vsCMElement))
Dim CE = p.CodeElement(t)
If CE IsNot Nothing Then
If CE.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes).Line = p.Line OrElse CE.GetStartPoint(vsCMPart.vsCMPartWhole).Line = p.Line Then _
Return CE
End If
Next t
Return Nothing
End Function
Metoda vrátí takový prvek kódu, který začíná na řádku, kde je kurzor. Přičemž "začíná" znamená, že tam začíná jeho hlavička nebo tam začínají jeho atributy.
Interface EnvDTE.CodeElement je je jakýsi společný předek všech elementů kódu. I když musím zde poznamenat, že objektový model Visual Studia je poněkud "divoký", protože je importován pomocí COM-interop (VS není napsáno v .NETu). Většina typů, jsou tedy "jen" interfacy. Pokud budete testovat typ objektu za běhu, dostanete jen System.__ComObject. Typy memají moc nějakou hierarchii dědičnosti, jak by se slušelo (CodeClass nedědí od CodeElement).
No nic. Máme tedy prvek pod kurzorem. Vlastnost CodeElement.Kind nám řekne, "co je to zač", a nyní již stačí vygenerovat správný XML komentář podle typu prvku. Pro výčty, konstanty a proměnné (fields) je to jednoduché - nic víc než <summary> není potřeba. Pro třídy, struktury a interfacy také - pokud ovšem nejsou generické. A tady je první problém. Visual studio vám přes rozhraní CodeClass2, CodeInterfase2 resp. CodeStruct2, řekne jestli se jedná o generický objekt nebo ne (IsGeneric), ale už vám neprozradí jména typových parametrů. Existuje zde možnost získat rozšiřující vlastnosti prvku, přes CodeClass2.Extender("VBGenericExtender"). Vlastnost vrátí objekt typu System.__ComObject. Po troše snažení zjistíte, že implementuje rozhraní IVBGenericExtender, ale to je konec :-(. Tento interface není definován v žádné knihovně, kterou jsem na počítači našel a v registrech je oněm uložena jediná informace - CLSID. A co je nejhorší - Google o něm mlčí (až naindexuje tenhle článek, už mlčet nebude ;-)!) Nemůžete tedy na tento typ přetypovat, neznáte jeho vlastnosti, nemůžete je volat ani přes late binding. Prostě nic. Bez alespoň základní podpory pro typové argumenty bych nemohl dobře spát, takže nezbývalo, než udělat jednoduchý parser. Tentokrát jsem jej postavil na stavovém automatu a jeho výstupem jsou jména generických parametrů použitelná pro tag <typeparam>. Viz metoda vbParseGeneric.
Parametry delegátů, funkcí a vlastností získáte jednoduše přes vlastnost Parameters příslušného typu. Pozor kolekce jsou indexovány od 1!. Pro generické funkce a delegáty platí to samé co pro typy.
Horší je to u událostí. V CLS totiž každá událost má přiřazeného delegáta, který určuje její parametry. Visual Basic vám však umožňuje deklarovat událost i s parametry a kompilátor vytvoří delegáta za vás. Porovnejte následující:
Public Event OnSthHappen(ByVal Sender As Object, ByVal e As EventArgs)
a
Public Event OnSthHappen As EventHandler
(Všichni jistě víte že existuje i třetí syntax Public Custom Event OnSthHappen As EventHandler
, která je podobná syntaxi pro vlastnosti.)
V prvním případě, je potřeba vygenerovat elementy <param>, ale VS vám parametry neprozradí, protože je u události neočekává. Je tedy potřeba záhlaví události rozparzovat ručně. Tentokráte jsem s lehce těžkým svědomím sáhl po regulárním výrazu ""^\s*Event\s+(_|\p{L})(_|\p{L}|\p{N})*\s*\(\s*((ByVal|ByRef)\s+((?List(Of List(Of String))
, ale nepochopí List(Of List(Of List(Of String)))
. Snad to pro začátek nebude vadit ;-). Výsledkem funkce vbParseEventHeader je opět seznam názvů parametrů události.
A je to! Tedy skoro. Ještě je nutné XML komentář na příslušný objekt kódu aplikovat, aniž by došlo například ke zníčení seznamu jeho uživatelských atributů. S tím nám VS naštěstí opět pomůže - příslušné rozhraní má vlastnost DocComment, do které stačí přiřadit. Teď už zbývá jen přesunout kurzor doprostřed tagu <summary> a uživatel může psát dokumentaci.
Toto makro vám umožňuje jednoduše "vykrádat" XML komentáře zobrazované v ObjectBrowseru. Hodí se to zejména pokud implementujete nějaký interface, nebo dědíte od nějaké třídy a overridujete její metody. Dokumentaci k nim mít chcete, ale rozhodně ji nechcete opisovat ručně. Najdete si tedy v object browseru příslušnou metodu, komentář skopírujete do schránky a na příslušném místě jej pomocí makra vložíte.
Další makro vám, ale ukáže, že to jde ještě jednodušeji, ještě pohodlněji.
Funkce XMLCommentFromObjectBrowser přeloží text z ObjectBrowseru na XML kometář. Texty v ObjectBrowseru mají naštěstí poměrně jednotný formát. Přesné pořadí sekcí odpovídající jednotlivým tagům. Každá sekce (kromě první) předcházena dvěma konci řádku. Pro skekce odpovídající tagům <param>, <typeparam> a <exception> je formát 'Název: Popis' následovaný koncem řádku. Žádné velké záludnoti na vás nečekají. ObjectBrowser k mému velkému naštvání ignoruje jakékoliv formátování jako <para>, <list> atp., ale alespoň k něčemu je to dobré. (Pokud chcete prohlížet komentáře i s formátováním doporučuji Lutz Roeder's .NET Reflector.) Takže si to přímo žádá regulární výraz. Je trošku dlouhý, ale s dobrým nástrojem (Rad Software Regular Expression Designer nebo Expresso) ho zvládne vytvořit i ... i já.
XML komentáře často obsahují odkazy na jiné typy, metody, vlastnosti atd. Ty se nejčastěji zavírají to tagu <see/>. O tuto informaci jsme převodem přes ObjectBrowser přišli. Metoda SeeReplace se snaží tuto "napáchanou křivdu" zmírnit alespoň tím, že obnoví reference ukazující do jmenných prostorů System a Microsoft. Pokud však často používáte i jiné jmenné prostory, není problém regulární výraz "(?<Name>(Microsoft|System)\.((Of\s)|[^\s\n\r])+)" rozšířit.
A na konec vložit vygenerované XML (momochodem použil jsem novou VB9 snytax pro XML) do souboru pomocí EditPoint.Insert.
Poslední makro dokáže téměř eliminovat použité toho předposledního. Vezme totiž metodu (vlastnostu, událost) pod kurzorem a připojí k ní XML kometář z místa její definice. To může být buď v bázové třídě nebo v nějakém interfacu.
Visual Studio nám s tímto úkolem opět trochu pomůže a trochu to nechá na nás. Pro metodu nebo vlastnost (ale i událost, kdet to nemá ve VB význam) se dozvíte jestli je deklarována Overrides a to takto: (This.OverrideKind And vsCMOverrideKind.vsCMOverrideKindOverride) = vsCMOverrideKind.vsCMOverrideKindOverride
(kde This je mého vlastního typu ClassMemberProvider, který v sobě spojuje CodeElement, CodeElement2, CodeProperty, CodeProperty2, CodeFunction, CodeFunction2 a CodeEvent - na objektový model už jsem nadával výše). Pokud se tedy dozvíme, že je deklarována Overrides, hledáme XML kometář v bázové třídě. Pokud se toto nedozvíme, pžedpokládáme že je použito Implements (s tím už nám VS nepomůže).
Hledání v bázové třídě probíhá jednoduše. Projsou se všechny její členy, vyberou se z nich ty stejného typu jako je ten, pro nějž XML kometář hledáme, porovnají se jejich jména a signatury a ten, který odpovídá je ten správný - do něj bude XML komentář vzat. U signatury bych se ještě trochu pozdržel. Příslušný objekt má vlastnost Prototype, která s parametrem 2 vrací string ukazující signaturu elementu. Signatura v tomto případě je např. "(,)" pro element se dvěma parametry. Není nic jednoduššího než tyto signatury stringově porovnat. Vystavujeme se zde jistému riziku vrácení špatného elementu, pokud se jedná o overload se stejným počtem parametrů ale jiného typu. Tuto nedeokonalost by bylo možné odstranit, protože VS podává informaci o typu parametrů - možná příště...
U interfaců je situace poněkud složitější. VB totiž dovoluje nazvat metodu, která implementuje jinou metodu jakkoliv a v klauzuli Implements říci, co implementuje. Mohl jsem opět parsovat klauzuli Implements, zístak z ní seznam implementovaných elementů rozhraní a po těch pak pátrat. Místo toho jsem se ale rozhodl pro tentokrát přenést trochu zodpovědnosti na uživatele. Pokud v dokumentu vybere nějaký text, bude se pátrat po metodě jejíž celé jméno (celé-jméno-interfacu.jméno-metody) odpovídá vybranému textu. Pokud není vybráno nic, bude se předpokládat, že názvy implementující a implementované metody se shodují. Toto pátrání obstará metoda SearchIMethod. Provede stejná porovnání jako byla zmíněna u hledání v bázové třídě, ale navíc pátrá i v rodičovských interfacech implementovaných interfaců a ukládá si do seznamu všechny metody (událoti/vlastnosti), jejichž jméno nějak odpovídá vybranému textu (pokud nějaký vybtaný je, jinak provede exaktní porovnání názvů metod). Nakonec vybere to z metod, jejíž celé jméno nejlépe odpovídá hledanému.
Na závěr je zde metoda SetXMLCommentFromOther, která jako parametry přijme instance ClassMemberProvider This a Other a nastaví XML kometář u This na stjenou hodnotu jako Other. Tato metoda si poradí jak s elementem Other, který jste si napsali sami, tak s tím, který je implementován v nějaké externí knihovně (třeba .NET Frameworku). Je zde však jistý rozdíl. Pro elementy, k jejichž zdrojovému kódu má VS přístup, dostanete přesně ten text, který je u nich zadán jako XML komentář. Pro elementy implementované "venku", jej ale dostanete obalen tagem <doc>, který by se ve vašem zdrojáku asi moc pěkně nevyjímal. Je potřeba tedy použít nějakou manipulaci s XML a vrátit jen "vnitřnosti" tagu <doc>. Nějak se mi toto nepodařilo vyrobit přes novou VB XML syntax a třídu XElement, ale vždy je tu ještě starý dobrý DOM a vlastnost InnerXml. Má jedinou nevýhodu: Celé XML vrací na jednom řádku. Je tedy potřeba vyložit konce řádků pře začátky těch XML tagů, které mají být na samotném řádku.
http://dzonny.cz/prj/VSMacros/Đ.rar
Závěrem bych především chtěl apelovat na všechny ať XML komentáře používají úplně na všechno na co se použít dají. Výrazně to zvyšuje možnost pochopení a použití kódu někým dalším.
Jinak budiž shrnuto, že Visual Studio poskytuje objektově orirntovaný přístup ke zdrojovému kódu na úrovni metod, vlastností, událotí, tříd, delegátů atd. Ne vždy je nám ale schopno poskytnout, vše co od něj chceme vědět. Jeho objektový model je založen na COM, takže se sním nepracuje úplně nejlépe, ale aspoň něco. VS můžeme skriptovat podobně jako Word a Excel, el programovacím jazykem zde není zastaralý, ošklivý a perverzní VBA ale pnohodnotný VB9. Kdysi jsem slyšel něco o podpoře pro C#, ale do Orcasu se evidentně "nevešla". Nutno je poznamnat, že makra nejsou zrovna vzory rychlosti - daleko pomalejší než v Officech. Je však možné psát pro VS i různé pluginy. Při tvorbě puginů pracujete se stejným COM objektovým modelem jako z maker ale nejdte omezeni na VB a pluginy jsou rychlejší.
Přeji vám mnoho ušetřeného klikání a opisování kódu a nastavte si ke komentářům nějakou pěknou barvičku, ať z nich máte radost ;-)!
Đonny