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

  1. GarykPatton - June 15, 2009

    How soon will you update your blog? I’m interested in reading some more information on this issue.

  2. Video Sexe - June 28, 2009

    nice! i’m gonna make my own blog

  3. Jovencitas Follando Moms - June 29, 2009

    now I’ll be tuned..

  4. Racing forums - November 25, 2009

    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.

  5. Yuri - June 13, 2010

    iienzuy@bzdelot.ru” rel=”nofollow”>1…

    no more…

Leave a Reply