How can I retrieve all of the code from a procedure, in which the cursor is positioned in a code window? I had previously written an article on this, but the code for it would not work consistently. For example, if a VB.NET Function or Sub definition line was continued, the code would raise an exception. Additionally, the code would not pick up C# "Block Comments". In order to get the code to work for all cases, both C# and VB.NET, I have rewritten this code and since it is fairly extensive, encapsulated all of the code in a Class. To get the code to work consistently, in all cases, for projects developed in both VB.NET and C#, I have resorted to the use of the FileCodeModel.
This article will describe the the VB.NET Code for this process. It will also describe the code for getting the desired method and any leading comments.
This article assumes that the developer has positioned the cursor somewhere in a Visual Basic or CSharp (C#) code window, in the Visual Studio .NET IDE. If the user has positioned the cursor, anywhere but inside a method, the GetWholeProcedure method will return an empty string. The add-in code will use the TextSelection and EditPoint objects to return the text of the whole procedure, in which the cursor was positioned, prior to triggering the add-in code to call the procedure.
The code for getting the whole procedure is now encapsulated in a class, since the complete code is quite extensive. To use the code in a macro, change the "applicationObject" to "DTE". Also, since only Subs may be called from the IDE, you will have to call a Sub which, in turn, calls the respective function.
The first thing that GetWholeProcedure does is to call a method named SearchForFunctionInCurrentWindow. This method, and its helper method, GetFunction, use the FileCodeModel to find a method that encompasses the current cursor position. If found, the CodeElement object exposes the start and end lines for the method. It places them in the Structure dimensioned as OneFunction. It then returns to the GetWholeProcedure. If the search method finds a method that encompasses the cursor position, the GetWholeProcedure method proceeds by calling a method, based on the code language for the project, to move the EditPoint object to the top of any comments that precede the selected method in the code window. If there are no comments, or there is an invalid block comment (C# only), the EditPoint will be left, or moved back to the beginning of the procedure. Finally, a determination is made to see whether the caller wanted the code to be highlighted or not. If the code is to be replaced, deleted, or moved, the caller will specify that the desired code is highlighted with the TextSelection object. Leaving the code highlighted will allow the caller to delete the code at a later time by using the Delete method of the TextSelection object. If the user does not want the code highlighted, I use the EditPoint object to retreive the code selection.
The call to GetWholeProcedure to retrieve a method from a VB.NET project code window, do the following:
| Dim S As String = GetWholeProcedure(True, True, 8) |
Dim S As String = GetWholeProcedure(True, True, 9) |
| ''' This class is designed to return a procedure ''' and any preceding comments for both vb and c# ''' from the cursor position in the active window. Imports Extensibility Imports EnvDTE Public Class CGetProc Private oVB As EnvDTE.DTE Private Structure SFunction Dim FName As String Dim StPt As Integer Dim EndPt As Integer Dim FCode As String Dim CCaret As Integer Dim BDone As Boolean End Structure Dim OneFunction As SFunction ' Returns code for procedure where caret was in ' the ActiveWindow. Lang must be 8 or 9 for VB or C#. ' Set withComments to True if you want any preceding comments ' returned with the proc. Set hiLite to true if you want the code ' selected in the code window. Public Function GetWholeProcedure(ByVal withComments As Boolean, _ ByVal hiLite As Boolean, _ ByVal lang As Short) As String Dim ts As TextSelection = oVB.ActiveDocument.Selection Dim ep As EditPoint = ts.ActivePoint.CreateEditPoint OneFunction.CCaret = ep.Line OneFunction.BDone = False SearchForFunctionInCurrentWindow() ' if we found the function, get the code If OneFunction.BDone Then ' move to the beginning of the function ep.MoveToLineAndOffset(OneFunction.StPt, 1) If withComments Then ' back the ep up to the beginning of any comments If lang = 8 Then PositionToTopOfVBComments(ep) Else PositionToTopOfCSComments(ep) End If End If ' get the code If Not hiLite Then Dim s As String = ep.GetLines(ep.Line, _ OneFunction.EndPt + 1) Return s Else ts.MoveToLineAndOffset(ep.Line, 1, False) ts.MoveToLineAndOffset(OneFunction.EndPt + 1, _ 1, True) Return ts.Text End If Else Return String.Empty End If End Function ' Move the editpoint to the first line of comments above ' the proc line, if any there. Private Sub PositionToTopOfVBComments(ByRef ep As EditPoint) Dim sLine As String Do ep.LineUp() ep.StartOfLine() sLine = ep.GetText(ep.LineLength).Trim If sLine.Length = 0 OrElse Not sLine.StartsWith("'") Then ep.LineDown() ep.StartOfLine() Exit Do End If Loop End Sub ' Move the editpoint to the first line of comments above ' the function line, if any there. Private Sub PositionToTopOfCSComments(ByRef ep As EditPoint) Dim s As String Dim stPT As Integer = ep.Line ' remember where we were ep.LineUp() ep.StartOfLine() s = ep.GetText(ep.LineLength).Trim If s.EndsWith("*/") And Not s.StartsWith("//") Then If s.StartsWith("/*") And _ (s.IndexOf("/*") = s.LastIndexOf("/*")) Then ' success, stop moving, 1 line blocked comment Exit Sub ElseIf s.IndexOf("/*") = -1 Then Do ep.LineUp() If ep.AtStartOfDocument Then ep.MoveToLineAndOffset(stPT, 1) Exit Do End If ep.StartOfLine() s = ep.GetText(ep.LineLength).Trim If s.IndexOf("/*") > -1 Then If s.StartsWith("/*") And _ (s.IndexOf("/*") = s.LastIndexOf("/*")) Then Exit Do Else ep.MoveToLineAndOffset(stPT, 1) Exit Do End If End If Loop Else ep.MoveToLineAndOffset(stPT, 1) Exit Sub End If ElseIf s.StartsWith("//") Then Do ep.LineUp() If ep.AtStartOfDocument Then ep.MoveToLineAndOffset(stPT, 1) Exit Sub End If ep.StartOfLine() s = ep.GetText(ep.LineLength).Trim If Not s.StartsWith("//") Then ep.LineDown() ep.StartOfLine() Exit Sub End If Loop Else ep.LineDown() End If End Sub ' Enumerates all classes in all namespaces in a code window. Private Sub SearchForFunctionInCurrentWindow() Dim pi As ProjectItem = oVB.ActiveWindow.ProjectItem Dim fcm As FileCodeModel = pi.FileCodeModel Dim ce As CodeElement If Not fcm Is Nothing Then GetFunction(fcm.CodeElements) End If End Sub Private Sub GetFunction(ByVal elements As CodeElements) ' ct could be a namespace or a class. ' we are looking only for functions ' and in particular, one that encapsulates ccaret ' this method will recursively drill down until ' it finds a function, if there is one and test ' its boundaries 'On Error Resume Next Dim members As CodeElements Dim elt As CodeElement Dim oneClass As New CClassList For Each elt In elements If OneFunction.BDone Then Exit Sub Debug.WriteLine(elt.Name & " " & _ elt.StartPoint.Line.ToString & " " & _ elt.EndPoint.Line.ToString) If elt.Kind = vsCMElement.vsCMElementFunction Then With OneFunction OneFunction.FName = elt.Name OneFunction.StPt = elt.StartPoint.Line OneFunction.EndPt = elt.EndPoint.Line If OneFunction.CCaret >= OneFunction.StPt AndAlso _ OneFunction.CCaret <= OneFunction.EndPt Then OneFunction.BDone = True Exit Sub End If End With Else members = GetMembers(elt) If Not members Is Nothing Then GetFunction(members) End If End If Next End Sub Private Function GetMembers(ByVal elt As CodeElement) As CodeElements Dim members As CodeElements = Nothing If Not elt Is Nothing Then ' set up to return the members collection ' based on the tye of codeelement Select Case elt.Kind Case vsCMElement.vsCMElementNamespace Dim cdeNS As CodeNamespace = CType(elt, CodeNamespace) members = cdeNS.Members Case vsCMElement.vsCMElementClass Dim cdeCl As CodeClass = CType(elt, CodeClass) members = cdeCl.Members Case vsCMElement.vsCMElementFunction ' functions dont have members return nothing End Select End If Return members End Function Public Sub New(ByRef roVB As EnvDTE.DTE) oVB = roVB End Sub End Class |