HOWTO: Get the code element at the cursor from a Visual Studio .NET macro or add-in

Author: Carlos J. Quintero (Microsoft MVP) Applies to: Microsoft Visual Studio .NET 2002
Date: January 2006   Microsoft Visual Studio .NET 2003
Updated: March 2013   Microsoft Visual Studio 2005
      Microsoft Visual Studio 2008
      Microsoft Visual Studio 2010
      Microsoft Visual Studio 2012

This article explains how to get the code element (class, property, procedure, etc.) at the position of the cursor in a code window.

More Information

To get the TextPoint object at the cursor position you can use:

CType(DTE.ActiveDocument, EnvDTE.TextDocument).Selection.ActivePoint

Once you have the TextPoint of the cursor you can get the code element that contains it using:

DTE.ActiveDocument.ProjectItem.FileCodeModel.CodeElementFromPoint(Point, Scope)

This method receives as parameters the text point and the scope (namespace, class, property, procedure, etc.) of the code element that you are interested in. However, the CodeElementFromPoint method is not reliable as you may notice experimenting a bit with different languages (at least using Visual Studio .NET 2002/2003): you can ask for a given code element kind and get another kind that you didn't request. For that reason it is safer to use another approach: walk recursively the code elements of a code file until you find the code element of the requested kind that contains the given text point.

The following macro illustrates this approach:

Sub GetCodeElementAtCursor()

   Dim objCodeElement As EnvDTE.CodeElement
   Dim objCursorTextPoint As EnvDTE.TextPoint

      objCursorTextPoint = GetCursorTextPoint()

      If Not (objCursorTextPoint Is Nothing) Then
         ' Get the class at the cursor
         objCodeElement = GetCodeElementAtTextPoint(vsCMElement.vsCMElementClass, _
            DTE.ActiveDocument.ProjectItem.FileCodeModel.CodeElements, objCursorTextPoint)
      End If

      If objCodeElement Is Nothing Then
         MessageBox.Show("No class found at the cursor!")
         MessageBox.Show("Class at the cursor: " & objCodeElement.FullName)
      End If

   Catch ex As System.Exception
   End Try

End Sub

Private Function GetCursorTextPoint() As EnvDTE.TextPoint

   Dim objTextDocument As EnvDTE.TextDocument
   Dim objCursorTextPoint As EnvDTE.TextPoint

      objTextDocument = CType(DTE.ActiveDocument.Object, EnvDTE.TextDocument)
      objCursorTextPoint = objTextDocument.Selection.ActivePoint()
   Catch ex As System.Exception
   End Try

   Return objCursorTextPoint

End Function

Private Function GetCodeElementAtTextPoint(ByVal eRequestedCodeElementKind As EnvDTE.vsCMElement, _
   ByVal colCodeElements As EnvDTE.CodeElements, ByVal objTextPoint As EnvDTE.TextPoint) _
   As EnvDTE.CodeElement

   Dim objCodeElement As EnvDTE.CodeElement
   Dim objResultCodeElement As EnvDTE.CodeElement
   Dim colCodeElementMembers As EnvDTE.CodeElements
   Dim objMemberCodeElement As EnvDTE.CodeElement

   If Not (colCodeElements Is Nothing) Then

      For Each objCodeElement In colCodeElements

         If objCodeElement.StartPoint.GreaterThan(objTextPoint) Then

            ' The code element starts beyond the point

         ElseIf objCodeElement.EndPoint.LessThan(objTextPoint) Then

            ' The code element ends before the point

         Else ' The code element contains the point

            If objCodeElement.Kind = eRequestedCodeElementKind Then
               ' Found
               objResultCodeElement = objCodeElement
            End If

            ' We enter in recursion, just in case there is an inner code element that also 
            ' satisfies the conditions, for example, if we are searching a namespace or a class
            colCodeElementMembers = GetCodeElementMembers(objCodeElement)

            objMemberCodeElement = GetCodeElementAtTextPoint(eRequestedCodeElementKind, _
               colCodeElementMembers, objTextPoint)

            If Not (objMemberCodeElement Is Nothing) Then
               ' A nested code element also satisfies the conditions
               objResultCodeElement = objMemberCodeElement
            End If

            Exit For

         End If


   End If

   Return objResultCodeElement

End Function

Private Function GetCodeElementMembers(ByVal objCodeElement As CodeElement) As EnvDTE.CodeElements

   Dim colCodeElements As EnvDTE.CodeElements

   If TypeOf objCodeElement Is EnvDTE.CodeNamespace Then

      colCodeElements = CType(objCodeElement, EnvDTE.CodeNamespace).Members

   ElseIf TypeOf objCodeElement Is EnvDTE.CodeType Then

      colCodeElements = CType(objCodeElement, EnvDTE.CodeType).Members

   ElseIf TypeOf objCodeElement Is EnvDTE.CodeFunction Then

      colCodeElements = DirectCast(objCodeElement, EnvDTE.CodeFunction).Parameters

   End If

   Return colCodeElements

End Function

Go back to the 'Resources for Visual Studio .NET extensibility' section for more articles like this