Documentation Template Via Visual Studio Macro
Consistency is the key when working on computer software, especially if you are developing a large system with multiple programmers. You should be consistent with the layout and format of your source code. Unfortunately this can be difficult to achieve when time is precious – and isn’t it always. However Visual Studios macro system makes it a breeze to automate this task with little to no work. Here is a simple way to implement this in C#.
What I would like to be able to do is execute a simple macro and see a dialog box that lets me enter information specific to the source file I am working with. That information will then be inserted into the file using a predefined template. The dialog will look something like this:

To accomplish this task we need to things; a .Net class library that implements the dialog and the macro to launch it and interact with Visual Studio using its automation model.
Creating the dialog is easy. Simply create a class library in the .Net language of your choice and add a form to it. Then make sure it has public properties that contain the information entered by the user so the macro can consume it. Once you have built your library you will have a .DLL file. This file needs to be copied to:
“C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PublicAssemblies”
for Visual Studio 2008. For earlier versions the location will be similar. This will make your dialog type available to your macro.
Next comes the macro. To do this open the Macros IDE using Tools->Macros->Macros IDE. Right click MyMacros in the IDE and select Add->New Module. Name the new module AutoDocCSharpSourceFile. Right click MyMacros again and select add reference. Add a reference to your class library. In your new macro add the following code:
Imports System Imports EnvDTE Imports EnvDTE80 Imports System.Diagnostics Imports System.Text.RegularExpressions Public Module AutoDocCSharpSourceFile Sub AutoDocCSharpSourceFile() ' This is the main entry point for the macro ' it will automatically document the c# source file selected ' by the user in the custom dialog using the custom template ' This should be used on new files only as it will not retain the ' existing code ' note that we only acknowlege Windows line endings (carrige return/line feed) ' if your source file is using a LF only you will have to replace all "\r\n" with "\n" ' also there is no error handling. You can catch any exceptions thrown and handle them ' nicely or you can just let Visual Studio catch them and show them to you ' make sure Visual Studio has a document open... of course this may not be a C# file If DTE.ActiveDocument Is Nothing Then MsgBox("Cannot Continue Because No Documents Are Open", MsgBoxStyle.OkOnly) Return End If ' first grab the text for the entire source file Dim sel As TextSelection = DTE.ActiveDocument.Selection sel.SelectAll() Dim documentText As String = sel.Text ' now extract the namespace name and the class name so ' we can populate the dialog for the user Dim namespaceName As String = GetNamespaceName(documentText) Dim className As String = GetClassName(documentText) ' display the dialog, the user can enter source file specific information ' such as the file description or rename the class etc... Dim dialog As New AutoTemplateDialog.AutoTemplateDialog(className, namespaceName) dialog.TopLevel = True dialog.ShowDialog() dialog.Focus() ' exit if user hit cancel If dialog.DialogResult = System.Windows.Forms.DialogResult.Cancel Then sel.StartOfDocument() dialog.Dispose() Return End If namespaceName = dialog.NamespaceName className = dialog.ClassName ' we no longer need the contents of the source file sel.Delete(sel.Text.Length) sel.StartOfDocument() ' now insert the template documentation GenFileDescription(dialog.Description) GenUsingStatements(documentText) GenFile(className, namespaceName, documentText) dialog.Dispose() ' it seems visual studio prevents automatic disposal? ' strange behavior results after a while if this isn't done End Sub Sub GenFileDescription(ByVal desc As String) ' This will generate the file description that goes at the top of ' the source file ' desc - A text description of the source file that will be inserted Dim fileDescription As String ' the file description that goes into the description tag If desc = String.Empty Then fileDescription = "//" + Environment.NewLine Else ' split the description into seperate lines so that we can add them into single line comments Dim lines As String() = Regex.Split(desc, "\r\n") ' comment each description line For Each line As String In lines fileDescription += ("// " + line + Environment.NewLine) Next End If ' now insert the header template DTE.ActiveDocument.Selection.Insert( _ "#region File Description" & vbNewLine & _ "//------------------------------------------------------------------------------" & vbNewLine & _ "// <file>" & vbNewLine & _ "// " & DTE.ActiveDocument.Name & vbNewLine & _ "// </file>" & vbNewLine & _ "//" & vbNewLine & _ "// <description>" & vbNewLine & _ fileDescription & _ "// </description>" & vbNewLine & _ "//" & vbNewLine & _ "//" & vbNewLine & _ "// <copyright file=""" & DTE.ActiveDocument.Name & """ company=""My Company"">" & vbNewLine & _ "// Copyright (c) 2009 My Company Inc. All rights reserved." & vbNewLine & _ "// </copyright>" & vbNewLine & _ "//------------------------------------------------------------------------------" & vbNewLine & _ "#endregion" & vbNewLine & vbNewLine) End Sub Sub GenUsingStatements(ByVal documentText As String) ' This will extract the using statements and wrap them ' in a region ' documentText is the body of the source file Dim mc As MatchCollection ' holds matches to the regular expression Dim ex As Regex ' regular expression for finding the using statements Dim nl As Regex ' holds maches for new lines ' find all using statements ex = New Regex("using") nl = New Regex("\n") mc = ex.Matches(documentText) ' start new region in source file DTE.ActiveDocument.Selection.Insert("#region Using Statements" & vbNewLine) ' now place each using statement in the region For Each m As Match In mc Dim x As Match = nl.Match(documentText.Substring(m.Index)) 'end statement at newline If x.Success Then DTE.ActiveDocument.Selection.Insert(documentText.Substring(m.Index, x.Index)) End If Next ' close off the region DTE.ActiveDocument.Selection.Insert("#endregion" & vbNewLine & vbNewLine) End Sub Function GetNamespaceName(ByVal documentText As String) As String ' finds the root namespace defined in the document and returns it Dim mc As Match ' matches in regular expression Dim ex As Regex Dim nl As Regex ' use regular expression to find the namespace name ex = New Regex("namespace[^\r\n]*") mc = ex.Match(documentText) ' return result If mc.Success Then Return mc.Value.Substring(10) ' using 10 will create a ' a problem if anything comes ' before the namespace keyword Else Return "" End If End Function Function GetClassName(ByVal documentText As String) As String ' finds the class name defined in the document Dim mc As Match Dim ex As Regex Dim nl As Regex ' use a regular expression just as in finding the namespace name ex = New Regex("class[^\r\n]*") mc = ex.Match(documentText) If mc.Success Then Return mc.Value.Substring(6) Else Return "" End If End Function Sub GenFile(ByVal className As String, ByVal namespaceName As String, ByVal documentText As String) ' now we will generate the body of the source file ' insert initial class and namespace declaration DTE.ActiveDocument.Selection.Insert("namespace " & namespaceName & vbNewLine & "{" & vbNewLine & vbTab & "public class " & className & vbNewLine & vbTab & "{" & vbNewLine) ' insert the regions of the body InsertRegion("Properties") InsertRegion("Events") InsertRegion("Constructors") InsertRegion("Public Methods") InsertRegion("Private Methods") InsertRegion("Fields") ' close namespace and class DTE.ActiveDocument.Selection.Insert(vbTab & "}" & vbNewLine & "}") End Sub Sub InsertRegion(ByVal name As String) ' just a helper routine to add a region statment to the source file DTE.ActiveDocument.Selection.Insert(vbTab & vbTab & "#region " & name & vbNewLine & vbTab & vbTab & "#endregion" & vbNewLine & vbNewLine) End Sub End Module
The macro is pretty straight forward with lots of documentation. It primarily is using the EnvDTE80.DTE2 class to interact with Visual Studio and write the source template.
Once you have saved the macro you can execute it by opening the Macro Explorer and running the AutoDocCSharpSourceFile macro. If you want you can also bind it to a key combination so you don’t have to use the explorer. You can accomplish this using the options dialog from the tools menu.
When you run the macro you should see a file header that looks like this:
#region File Description //------------------------------------------------------------------------------ // <file> // Program.cs // </file> // // <description> // This is a TEST! // </description> // // // <copyright file="Program.cs" company="My Company"> // Copyright (c) 2009 My Company Inc. All rights reserved. // </copyright> //------------------------------------------------------------------------------ #endregion
And several regions reserved for specific C# constructs. The one issue with the current implementation is that it only preserves a single class name, the namespace name, and the using directives. Anything else in the file is lost. I’ve also noticed a now and then on Vista the dialog appears behind Visual Studio, if that’s the case just alt-tab to get it back.
I Hope this proves helpful to you and saves you some time. I would love to hear feedback on any issues or improvements that could be made.
Here is my Visual Studio solution that implements the dialog window as well as the VB script macro.
April 2, 2009
Posted in: General

5 Responses
How soon will you update your blog? I’m interested in reading some more information on this issue.
nice! i’m gonna make my own blog
now I’ll be tuned..
These comments “gonna make my own blog” are just spam and it’s the way how I found your blog. Bear in mind that spammers are doing this too and once they find blogs that aren’t secured against spam, they keep bombing it with comments. Consider using some captcha plugin that really works.
Yuri - June 13, 2010
iienzuy@bzdelot.ru” rel=”nofollow”>1…
no more…
Leave a Reply