ForEachMethodInFile is a Visual Studio macro that lets you do custom actions for each method defined in the current file. I’ve used it in the past to generate logging code to log the start and end of each method, to generate default error handling code etc.. And today someone over at CodeProject wanted to generate a breakpoint at the start of each method. The common thread here is the performing of some custom action for each method in the current file. I figured it would be useful for a lot of people if the “for each method in file” logic is available as a separate library, and that is what is ForEachMethodInFile.
The macro project is available here. To provide a custom action, all you have to do is create a new macro project, and do
1: Imports System
2: Imports EnvDTE
3: Imports EnvDTE80
4: Imports EnvDTE90
5: Imports System.Diagnostics
6: Imports ForEachMethodInFile
7:
8: Public Module BreakOnEachMethodEntry
9:
10: Public Sub Run()
11: ForEachMethod.DoAction(AddressOf AddBreakPoint)
12: End Sub
13:
14: Function AddBreakPoint(ByVal method As CodeFunction)
15: DTE.Debugger.Breakpoints.Add(method.Name)
16: End Function
17:
18: End Module
You define a callback function that takes the COM object representing a method as a parameter, and then pass on the function to the DoAction method. Your function will be called for each method defined in the file, and the cursor will be positioned at the start of the method before the callback occurs. In this case, I want to add a breakpoint, so I call the appropriate DTE method, passing the current method’s name to tell it where to place the breakpoint.
Here’s the entire code in the ForEachMethodInFile project – it basically recursively traverses code elements in the file, and invokes the callback when it runs into a method.
1: Imports System
2: Imports EnvDTE
3: Imports EnvDTE80
4: Imports System.Diagnostics
5:
6: Public Module ForEachMethod
7:
8: Public Sub DoAction(ByRef action As Action(Of CodeFunction))
9: ProcessFile(action)
10: End Sub
11:
12: Function ProcessFile(ByRef action As Action(Of CodeFunction))
13: Dim selection As EnvDTE.TextSelection
14: Dim projectItem As ProjectItem
15: Dim fileCodeModel As FileCodeModel
16: Dim codeElement As CodeElement
17: Dim i As Integer
18:
19: Dim currentFunction As CodeFunction
20:
21: projectItem = DTE.ActiveDocument.ProjectItem
22:
23: fileCodeModel = projectItem.FileCodeModel
24: For i = 1 To fileCodeModel.CodeElements.Count
25: codeElement = fileCodeModel.CodeElements.Item(i)
26: ProcessCodeElement(codeElement, action)
27: Next
28:
29: ' Reformat the modified code
30: selection = DTE.ActiveDocument.Selection
31: selection.SelectAll()
32: selection.SmartFormat()
33: End Function
34:
35: Sub ProcessNamespace(ByVal namespaceElement As CodeNamespace, ByRef action As Action(Of CodeFunction))
36: Dim i As Integer
37: Dim codeElement As CodeElement
38:
39: For i = 1 To namespaceElement.Members.Count
40: codeElement = namespaceElement.Members.Item(i)
41: ProcessCodeElement(codeElement, action)
42: Next
43: End Sub
44:
45: Sub ProcessCodeElement(ByVal codeElement As CodeElement, ByRef action As Action(Of CodeFunction))
46: If codeElement.Kind = vsCMElement.vsCMElementNamespace Then
47: ProcessNamespace(codeElement, action)
48: ElseIf codeElement.Kind = vsCMElement.vsCMElementClass Then
49: ProcessType(codeElement, action)
50: ElseIf codeElement.Kind = vsCMElement.vsCMElementFunction Then
51: ProcessMethod(codeElement, action)
52: End If
53: End Sub
54:
55: Sub ProcessType(ByVal typeElement As CodeClass, ByRef action As Action(Of CodeFunction))
56: Dim i As Integer
57: Dim codeElement As CodeElement
58:
59: For i = 1 To typeElement.Members.Count
60: codeElement = typeElement.Members.Item(i)
61: If codeElement.Kind = vsCMElement.vsCMElementFunction Then
62: ProcessMethod(codeElement, action)
63: ElseIf codeElement.Kind = vsCMElement.vsCMElementClass Then
64: ProcessType(codeElement, action)
65: End If
66: Next
67: End Sub
68:
69: Sub ProcessMethod(ByVal methodElement As CodeFunction, ByRef action As Action(Of CodeFunction))
70: Dim selection As EnvDTE.TextSelection
71: Dim editPoint As EnvDTE.EditPoint
72: Dim verifyPoint As EnvDTE.TextPoint
73: Dim endPointAbsCharOffset As Integer
74: Dim column As Integer
75: Dim methodRunNotifierSignature As String
76: Dim functionStartCode As String
77: Dim functionEndCode As String
78: Dim parameters As String
79: Dim parameter As EnvDTE80.CodeParameter2
80: Dim i As Integer
81:
82: If methodElement.MustImplement Then
83: Return
84: End If
85:
86: selection = DTE.ActiveDocument.Selection
87: editPoint = selection.ActivePoint.CreateEditPoint()
88: verifyPoint = selection.ActivePoint.CreateEditPoint()
89:
90: ' Move to start of method
91: editPoint.MoveToPoint(methodElement.GetStartPoint(vsCMPart.vsCMPartBody))
92: selection.MoveToPoint(editPoint)
93: verifyPoint.MoveToPoint(methodElement.GetStartPoint(vsCMPart.vsCMPartBody))
94:
95: action(methodElement)
96:
97: End Sub
98: End Module
99:
100:
WinMacro is a tiny little application that can record and replay keyboard and mouse actions that you do on your Windows desktop. It’s similar to the macro facility in Word and Excel, but works across applications.
I wrote the initial version nearly 5 years back, and it proved to be very popular, with approximately 30000 downloads and tons of email from users. Most of the emails were appreciative, and some of them touching, especially those that described how WinMacro was helping people do a better job in fields ranging from cancer research to network testing.
There were quite a few feature requests and bug reports too. WinMacro 2.0 (Beta) (http://winmacro.codeplex.com/) attempts to addresses some of them. The list of new features is available in the download page.
That apart, it was “interesting” to look at code I’d written 5 years ago. I found it positively revolting, to say the least. Lots of copy pasted code, no error handling and plenty of global variables and convoluted code made it scored very heavily in the WTF scale. And this was code I was rather proud of, at that time.
On the flip side, the fact that I found my old code disgusting means I’ve improved my coding skills enough to make that code look terrible. But that is still relative improvement though, it remains to be seen how much I score on the absolute WTF scale, if there is one :)