How to choose functions in Visual Studio with the keyboard

In Visual Studio, there’s pretty good support for keys, but for me it lacks one significant feature. That is the ability to jump to a function from inside a class. That is, if you’ve got a class open the text editor, you want to see a list of functions in the class, choose one, and navigate to it, via the keyboard. You’ll be familiar with it from the two drop-down lists at the top of the code window; however, this drop-down can’t be activated by keyboard.

Here’s a cheap and cheerful implementation of the same features. Put it into your macro module, and map the macro to a keyboard shortcut.

Sub ChooseFunctionAndNavigateInIde()
Try
Dim functionIndex As New Dictionary(Of String, Integer)

Dim doc As Document = DTE.ActiveWindow.Document
For Each elem As CodeElement In doc.ProjectItem.FileCodeModel.CodeElements
BuildCodeElements(elem, functionIndex, 0)
Next

Dim result As Integer = FChooser.SelectItem(functionIndex)
If (result = -1) Then
‘ user cancelled
Return
Else
Dim sel As TextSelection = CType(doc.Selection, TextSelection)
sel.GotoLine(result, True)
End If
Catch ex As Exception
End Try
End Sub

Private Sub BuildCodeElements(ByVal elem As CodeElement, ByVal functionIndex As Dictionary(Of String, Integer), ByVal depth As Integer)

Try
Select Case elem.Kind
Case vsCMElement.vsCMElementClass, vsCMElement.vsCMElementDelegate, vsCMElement.vsCMElementEnum, vsCMElement.vsCMElementEvent, vsCMElement.vsCMElementEventsDeclaration, vsCMElement.vsCMElementFunction, vsCMElement.vsCMElementInterface, vsCMElement.vsCMElementModule, vsCMElement.vsCMElementProperty
Dim name As String = Space(depth * 4) & elem.Name
Dim line As Integer = elem.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes).Line
functionIndex.Add(name, line)
Case Else

End Select

Catch ex As Exception

End Try

For Each child As CodeElement In elem.Children
BuildCodeElements(child, functionIndex, depth + 1)
Next
End Sub

Private Class FChooser
Inherits Form

Dim lv As ListView

Public Shared Function SelectItem(ByVal list As Dictionary(Of String, Integer)) As Integer
Dim chooser As New FChooser(List)
chooser.BringToFront()
chooser.ShowDialog()
Return chooser.Result
End Function

Public Sub New(ByVal list As Dictionary(Of String, Integer))
lv = New ListView()
lv.Dock = DockStyle.Fill
lv.View = View.List

For Each kvp As KeyValuePair(Of String, Integer) In list
Dim lvi As ListViewItem = lv.Items.Add(kvp.Key)
lvi.Tag = kvp.Value
Next
Me.Controls.Add(lv)

AddHandler lv.KeyUp, AddressOf Key
AddHandler lv.DoubleClick, AddressOf DoubleClick

End Sub

Public Result As Integer = -1

Public Sub DoubleClick(ByVal sender As Object, ByVal e As EventArgs)
If (Me.lv.SelectedItems.Count = 1) Then
Me.Result = CType(lv.SelectedItems(0).Tag, Integer)
Me.Close()
End If
End Sub

Public Sub Key(ByVal sender As Object, ByVal e As KeyEventArgs)
If (e.KeyCode = Keys.Enter) Then
Me.Result = CType(lv.SelectedItems(0).Tag, Integer)
Me.Close()
ElseIf (e.KeyCode = Keys.Escape) Then
Result = -1
Me.Close()
Else
‘ could be a letter, and therefore a seek
Dim letter As String = e.KeyCode.ToString().ToLower()
If letter.Length = 1 And Regex.IsMatch(letter, “[a-z0-9]”) Then
‘ find the first entry starting with this letter

Dim found As ListViewItem = Nothing
For Each lvi As ListViewItem In Me.lv.Items
Dim lviText As String = lvi.Text.Trim().ToLower()
If (found Is Nothing And lviText.StartsWith(letter)) Then
found = lvi
End If
Next

If (found Is Nothing = False) Then
For Each lvi As ListViewItem In Me.lv.Items
lvi.Selected = False
Next
found.Selected = True
End If
End If
End If
End Sub

End Class