How many times have you defined a block of code, such as an if statement? You begin the statement, followed by the conditions "if ([condition(s)])". You then follow a pattern similar to the following:
- Press Enter to advance a line
- Start the block with a left brace ({)
- Press Enter to advance a line
- End the block with a right brace (})
- Press Up so you're behind the left brace
- Press Enter to add a line between the braces
I quickly grew tired of repeating that bulleted list every time I wanted to define a block of code (classes, properties, methods, if statements, switch statements, using statements, etc.). I found a fast, and relatively easy way to automate this task for me: Visual Studio Macros. (Note: the reason for the "relatively" part is due to the VB.NET language requirement for macros)
This is the code I wrote to define a block for me:
Sub AutoBrace()
Dim sel As EnvDTE.TextSelection
sel = DTE.ActiveWindow.Selection
DTE.UndoContext.Open("Auto Brace")
Try
sel.EndOfLine()
sel.NewLine()
sel.Unindent()
sel.Text = "{"
sel.EndOfLine()
sel.NewLine()
sel.Unindent()
sel.Text = "}"
sel.LineUp()
sel.EndOfLine()
sel.NewLine()
Finally
DTE.UndoContext.Close()
End Try
End Sub
Since my attempt to automate block definition turned out so well, I decided to take a stab at automating another repetative task I routinely performed: region directives.
In general, I like to use region directives around methods, properties, etc. for outlining. Again, I figured that there had to be a way to automate this. This macro proved to be quite a bit tougher than the last one, but I was able to automate the task.
#region SomeMethod
public void SomeMethod()
{
...
}
#endregion
This is the macro that I wrote to handle generation of region directives around the code element that the cursor was within. Basically, it finds the inner-most code element that the cursor is in, locates the start and end points, and wraps them in a #region directive.
Sub AutoRegion()
Dim sel As EnvDTE.TextSelection
Dim selPoint As EnvDTE.EditPoint
Dim editPoint As EnvDTE.EditPoint
Dim elem As EnvDTE.CodeElement
sel = DTE.ActiveWindow.Selection
selPoint = sel.ActivePoint.CreateEditPoint
editPoint = sel.ActivePoint.CreateEditPoint
elem = GetCurrentCodeElement(editPoint)
DTE.UndoContext.Open("Auto Region")
Try
editPoint.MoveToPoint(elem.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes))
editPoint.LineUp()
Do While IsCommentLine(editPoint, "///")
editPoint.LineUp()
Loop
editPoint.EndOfLine()
sel.MoveToPoint(editPoint, False)
sel.NewLine()
sel.Text = "#region " + elem.Name
editPoint.MoveToPoint(selPoint)
editPoint.MoveToPoint(elem.GetEndPoint(vsCMPart.vsCMPartWholeWithAttributes))
sel.MoveToPoint(editPoint, False)
sel.NewLine()
sel.Text = "#endregion"
sel.MoveToPoint(selPoint)
Finally
DTE.UndoContext.Close()
End Try
End Sub
The first issue I ran into was how to find the inner-most element at the cursor location. The best method of doing this, at the time, was to step through a list of possible elements, returning the first that didn't throw an exception (dirty, I know, but I already felt a little dirty working in VB.NET anyway).
Private Function GetCurrentCodeElement(ByVal editPoint As EnvDTE.EditPoint) As EnvDTE.CodeElement
Dim elem As EnvDTE.CodeElement
Dim types As vsCMElement() = _
{vsCMElement.vsCMElementProperty, _
vsCMElement.vsCMElementFunction, _
vsCMElement.vsCMElementEnum, _
vsCMElement.vsCMElementInterface, _
vsCMElement.vsCMElementStruct, _
vsCMElement.vsCMElementClass}
Dim type As vsCMElement
For Each type In types
elem = GetCodeElement(editPoint, type)
If Not elem Is Nothing Then
Exit For
End If
Next
GetCurrentCodeElement = elem
End Function
Private Function GetCodeElement(ByVal editPoint As EnvDTE.EditPoint, ByVal type As vsCMElement) As EnvDTE.CodeElement
Dim elem As EnvDTE.CodeElement
Try
elem = editPoint.CodeElement(type)
Catch ex As Exception
elem = Nothing
End Try
GetCodeElement = elem
End Function
I also wanted the macro to be able to handle XML comment tags for the target code element as well.
Private Function IsCommentLine(ByVal editPoint As EnvDTE.EditPoint, ByVal linePrefix As String) As Boolean
Dim lineText As String
Dim prefixText As String
EditPoint.StartOfLine()
lineText = EditPoint.GetText(EditPoint.LineLength).Trim
prefixText = linePrefix.Trim
If lineText.Equals(prefixText) Or lineText.StartsWith(prefixText) Then
IsCommentLine = True
Else
IsCommentLine = False
End If
End Function
I have attached a zip containing the source file here. Happy automating!
* edited to correct spelling mistake (consicions -> conditions)