|
|
Use the CodeModel to retrieve a procedure from a code window.GetWholeProcedure | | 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
|
|