KnowDotNet

Use the CodeModel to retrieve a procedure from a code window.

GetWholeProcedure

by Les Smith

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)


To retrieve a method from a C# project, use the following line of code:


  
Dim S As String = GetWholeProcedure(True, True, 9)


Figure 1 - VB.NET Add-in Code.

''' 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
Go to Top