Skip to content

Instantly share code, notes, and snippets.

@agross
Created September 2, 2010 09:22
Show Gist options
  • Save agross/562087 to your computer and use it in GitHub Desktop.
Save agross/562087 to your computer and use it in GitHub Desktop.
A Visual Studio macro that exports key bindings for ReSharper and a set of custom bindings
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Imports System.Collections.Generic
' Comment the following line if you're running the macro in Visual Studio .NET 2003.
Imports Microsoft.VisualStudio.CommandBars
Class CommandBarRecursion
Shared ShowExcessiveDebugOutput As Boolean = False
Public Delegate Function CommandBarControlFilter(ByVal control As CommandBarControl) As Boolean
Public Delegate Function CommandFilter(ByVal command As Command) As Boolean
' Recurses a collection of CommandBars returning a list of CommandBarControls matching the specified filter.
Public Shared Function RecurseCommandBars(ByVal commandBars As CommandBars, ByVal filter As CommandBarControlFilter) As List(Of CommandBarControl)
Dim result As List(Of CommandBarControl)
result = New List(Of CommandBarControl)()
For Each bar As CommandBar In CType(DTE.CommandBars, CommandBars)
Debug.WriteLine(String.Format("Processing command bar: {0}", GetPath(bar)))
result.AddRange(RecurseCommandBar(bar.Controls, filter))
Next
Return result
End Function
' Recurses a collection of CommandBarControls returning a list of CommandBarControls matching the specified filter.
Public Shared Function RecurseCommandBar(ByVal controls As CommandBarControls, ByVal filter As CommandBarControlFilter) As List(Of CommandBarControl)
Dim result As List(Of CommandBarControl)
result = New List(Of CommandBarControl)()
For Each control As CommandBarControl In controls
If ShowExcessiveDebugOutput Then
Debug.WriteLine(String.Format("Processing command bar control: {0}", GetPath(control)))
End If
If filter <> Nothing Then
If filter(control) Then
result.Add(control)
End If
End If
' Recurse childs.
If control.accChildCount > 0 Then
If control.Type = MsoControlType.msoControlPopup Then
result.AddRange(RecurseCommandBar(CType(control, CommandBarPopup).Controls, filter))
End If
End If
Next
Return result
End Function
' Deletes a list of command bar controls.
Public Shared Sub DeleteCommandBarControls(ByVal commandBarControls As List(Of CommandBarControl), ByVal testMode As Boolean)
Dim testModeMessage As String
If testMode Then
testModeMessage = "Test mode"
Else
testModeMessage = "Committing changes"
End If
Debug.WriteLine(String.Format("Deleting {0} command bar controls ({1})", commandBarControls.Count, testModeMessage))
For Each commandBarControl As CommandBarControl In commandBarControls
Try
Debug.WriteLine(String.Format("Deleting command bar control: {0} ({1})", GetPath(commandBarControl), testModeMessage))
Catch ex As System.Runtime.InteropServices.COMException
End Try
Try
If testMode = False Then
commandBarControl.Delete()
End If
Catch ex As System.Runtime.InteropServices.COMException
Debug.WriteLine(String.Format("Error deleting command bar control: {0}", GetPath(commandBarControl)))
End Try
Next
End Sub
' Enumerates all commands returning a list of commands matching the specified filter.
Public Shared Function EnumerateCommands(ByVal commands As Commands, ByVal filter As CommandFilter) As List(Of Command)
Debug.WriteLine(String.Format("Enumerating {0} commands", commands.Count))
Dim result As List(Of Command)
result = New List(Of Command)()
For Each command As Command In commands
If ShowExcessiveDebugOutput Then
Debug.WriteLine(String.Format("Processing command: {0}", command.Name))
End If
If filter <> Nothing Then
If filter(command) Then
result.Add(command)
End If
End If
Next
Return result
End Function
' Deletes a list of commands.
Public Shared Sub DeleteCommands(ByVal commands As List(Of Command), ByVal testMode As Boolean)
Dim testModeMessage As String
If testMode Then
testModeMessage = "Test mode"
Else
testModeMessage = "Committing changes"
End If
Debug.WriteLine(String.Format("Deleting {0} commands ({1})", commands.Count, testModeMessage))
For Each command As Command In commands
Debug.WriteLine(String.Format("Deleting command: {0} ({1})", command.Name, testModeMessage))
If testMode = False Then
Try
command.Delete()
Catch ex As System.Runtime.InteropServices.COMException
Debug.WriteLine(String.Format("Error deleting command: {0}", command.Name))
End Try
End If
Next
End Sub
' Returns the path to a command bar control.
Shared Function GetPath(ByVal control As Object) As String
If TypeOf (control) Is CommandBarControl Then
Dim cbc As CommandBarControl
cbc = CType(control, CommandBarControl)
Return GetPath(cbc.Parent) + "->" + cbc.Caption
End If
If TypeOf (control) Is CommandBar Then
Dim cb As CommandBar
cb = CType(control, CommandBar)
Return GetPath(cb.Parent) + "->" + cb.Name
End If
Return "DTE"
End Function
End Class
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Imports System.Globalization
Class CommandFilter
Friend Shared CustomCommands As String()
Shared CurrentCommandName As String
Friend Shared Function FilterReSharperCommands(ByVal command As Command) As Boolean
If command.Name <> Nothing Then
If CultureInfo.InvariantCulture.CompareInfo.IndexOf(command.Name, "ReSharper", CompareOptions.OrdinalIgnoreCase) <> -1 Then
Return True
End If
End If
Return False
End Function
Friend Shared Function FilterCustomCommands(ByVal command As Command) As Boolean
If command.Name <> Nothing Then
CurrentCommandName = command.Name
Return Array.Exists(Of String)(CustomCommands, AddressOf CompareCommandName)
End If
Return False
End Function
Shared Function CompareCommandName(ByVal commandName As String) As Boolean
Return commandName.Equals(CurrentCommandName, StringComparison.InvariantCultureIgnoreCase)
End Function
Friend Shared Function CommandSorter(ByVal x As Command, ByVal y As Command) As Integer
Return x.Name.CompareTo(y.Name)
End Function
End Class
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Imports System.Collections.Generic
Imports System.IO
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Windows.Forms
Public Module KeyBindings
' Export/import file.
Const Filename As String = "C:\path\to\save\the\shortcuts\Keyboard Shortcuts.txt"
' Additional shortcuts to export. "Resharper*" is exported automatically -- see the Helper class.
ReadOnly _vsCommands As String() = New String() {"Edit.GoTo", _
"Edit.GoToPrevLocation", _
"Edit.LineDelete", _
"File.CloseAllButThis", _
"TestDriven.NET.RerunTests", _
"TestDriven.NET.RunTests", _
"Window.CloseAllDocuments", _
"Window.CloseDocumentWindow"}
Public Sub ExportKeyboardBindings()
Dim shortcuts = New ShortcutCollection()
' ReSharper bindings.
Dim resharperCommands = CommandBarRecursion.EnumerateCommands(CType(DTE.Commands, Commands), AddressOf CommandFilter.FilterReSharperCommands)
AddCommandBindingsTo(shortcuts, resharperCommands)
' Additional Visual Studio bindings.
CommandFilter.CustomCommands = _vsCommands
Dim vsCommands = CommandBarRecursion.EnumerateCommands(CType(DTE.Commands, Commands), AddressOf CommandFilter.FilterCustomCommands)
AddCommandBindingsTo(shortcuts, vsCommands)
SerializeShortcuts(shortcuts, Filename)
End Sub
Sub AddCommandBindingsTo(ByRef shortcuts As ShortcutCollection, ByVal commands As List(Of Command))
For Each command In commands
Dim bindings = CType(command.Bindings, Object())
For Each binding In bindings
shortcuts.Add(New Shortcut(command.Name, binding.ToString()))
Next
Next
End Sub
Sub SerializeShortcuts(ByVal shortcuts As ShortcutCollection, ByVal filename As String)
Dim file = New StringBuilder()
For Each shortcut In shortcuts
file.AppendLine(shortcut.ToString())
Next
Debug.WriteLine(file.ToString())
System.IO.File.WriteAllText(filename, file.ToString(), Encoding.UTF8)
End Sub
Public Sub ImportKeyboardBindings()
Dim file = System.IO.File.ReadAllText(Filename, Encoding.UTF8)
Dim commands = CType(DTE.Commands, Commands)
For Each shortcut As Shortcut In Shortcut.ParseFile(file)
Debug.WriteLine(shortcut.ToString())
Try
Dim command = commands.Item(shortcut.Command)
Dim bindings = CType(command.Bindings, Object())
Array.Resize(bindings, bindings.Length + 1)
Array.Reverse(bindings)
bindings(0) = shortcut.Shortcuts(0)
' Comment the following two lines if the new binding should be added to the list of bindings
' instead of replacing the current bindings.
Array.Resize(bindings, shortcut.Shortcuts.Count)
shortcut.Shortcuts.ToArray().CopyTo(bindings, 0)
command.Bindings = bindings
Catch ex As Exception
Debug.WriteLine(String.Format("Error: {0}", ex.Message))
MessageBox.Show(String.Format("Error while importing {0}: {1}", _
shortcut, _
ex.Message), _
"Error", _
MessageBoxButtons.OK, _
MessageBoxIcon.Error)
End Try
Next
End Sub
End Module
Imports System
Imports System.Text.RegularExpressions
Imports System.Collections.Generic
Public Class Shortcut
Implements IEquatable(Of Shortcut), IComparable(Of Shortcut)
Private _command As String
Private _shortcuts As List(Of String) = New List(Of String)
' A description of the regular expression:
'
' Beginning of line or string
' [Command]: A named capture group. [.+]
' Any character, one or more repetitions
' ->
' Space
' ->
' Space
' [Shortcut]: A named capture group. [.*]
' Any character, any number of repetitions
' \r$
' Carriage return
' End of line or string
Private Shared _regex As Regex = New Regex("^(?<Command>.+) -> (?<Shortcut>.*)\r$", _
RegexOptions.IgnoreCase _
Or RegexOptions.Multiline _
Or RegexOptions.CultureInvariant _
Or RegexOptions.Compiled)
Public Property Shortcuts() As List(Of String)
Get
Return _shortcuts
End Get
Private Set(ByVal value As List(Of String))
_shortcuts = value
End Set
End Property
Public Property Command() As String
Get
Return _command
End Get
Private Set(ByVal value As String)
_command = value
End Set
End Property
Public Sub New(ByVal command As String, ByVal shortcut As String)
Me.Command = command
Me.Shortcuts.Add(shortcut)
End Sub
Public Overrides Function ToString() As String
Shortcuts.Sort()
Dim shortcutArray = Shortcuts.ConvertAll(New Converter(Of String, String)(AddressOf ShortcutToParsableString)).ToArray()
Return String.Join(Environment.NewLine, shortcutArray)
End Function
Public Function ShortcutToParsableString(ByVal shortcut As String) As String
Return String.Format("{0} -> {1}", Command, shortcut)
End Function
Public Overrides Function GetHashCode() As Integer
Return Command.GetHashCode()
End Function
Public Function CompareTo(ByVal other As Shortcut) As Integer Implements IComparable(Of Shortcut).CompareTo
Return Command.CompareTo(other.Command)
End Function
Public Function Equals(ByVal other As Shortcut) As Boolean Implements IEquatable(Of Shortcut).Equals
Return Me.CompareTo(other) = 0
End Function
Public Shared Function ParseFile(ByVal contents As String) As IEnumerable(Of Shortcut)
Dim result As Dictionary(Of Shortcut, Shortcut) = New Dictionary(Of Shortcut, Shortcut)
For Each lineMatch As Match In _regex.Matches(contents)
Dim command As String = lineMatch.Groups("Command").Value
Dim shortcut As String = lineMatch.Groups("Shortcut").Value
Dim parsed As Shortcut = New Shortcut(command, shortcut)
If result.ContainsKey(parsed) Then
result(parsed).Shortcuts.Add(shortcut)
Else
result.Add(parsed, parsed)
End If
Next
Return result.Values
End Function
End Class
Imports System
Imports System.Collections
Imports System.Collections.Generic
Public Class ShortcutCollection
Implements IEnumerable(Of Shortcut)
Private _inner As List(Of Shortcut) = New List(Of Shortcut)
Sub Add(ByVal item As Shortcut)
If _inner.Contains(item) Then
_inner(_inner.IndexOf(item)).Shortcuts.AddRange(item.Shortcuts)
Else
_inner.Add(item)
End If
End Sub
ReadOnly Property Count() As Integer
Get
Return _inner.Count
End Get
End Property
Function GetEnumerator() As IEnumerator(Of Shortcut) Implements IEnumerable(Of Shortcut).GetEnumerator
_inner.Sort()
Return _inner.GetEnumerator
End Function
Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
_inner.Sort()
Return _inner.GetEnumerator
End Function
End Class
@ChristianDeger
Copy link

Thanks for those handy scripts!
But there is a reference to "CommandBarRecursion" which seems to be missing in your gist.

@agross
Copy link
Author

agross commented Sep 3, 2010

Sorry, it should be fixed now. Please see the revision 532e5c.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment