Monday, July 20, 2009

Emacs Keybindings in Visual Studio

I've been using the Emacs keybindings in Visual Studio for a little while. There was just one thing that drove me crazy. It would not autoindent into the current line, nor would it indent when you hit TAB. You actually had to have source code on the line before TAB would do a smart indent.

Someone from Microsoft had originally written some Visual Studio macros to address this. You could rebind TAB to just do a plain old tab insert. Of course the source code URL I found was no longer valid... So I contacted the engineer at Microsoft. He got back to me and told me that he no longer had the source, but that it shouldn't be too hard to write my own.

It took me about 15 or 20 minutes to get it correct. You have to use the Macros IDE to add a module under "MyMacros". I called mine "EditorMacros". You should unbind "Edit.InsertTab" and rebind it to the sub routine below ("Macros.MyMacros.EditorMacros.EmacsInsertTab.") It looks like this


Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics

Public Module EditorMacros

'' Please insert a TAB!
Sub EmacsInsertTab()
Dim editPoint As EnvDTE.EditPoint
Dim selection As EnvDTE.TextSelection
Dim startPoint As EnvDTE.EditPoint
Dim currentPoint As EnvDTE.EditPoint
Dim endPoint As TextPoint

selection = DTE.ActiveDocument.Selection
If (selection.IsEmpty()) Then
editPoint = selection.ActivePoint.CreateEditPoint()

'' 0x09 is an ASCII horizontal tab
editPoint.Insert(Microsoft.VisualBasic.Constants.vbTab)
Else
startPoint = selection.TopPoint.CreateEditPoint()
endPoint = selection.BottomPoint
currentPoint = startPoint
Do While (True)
Dim line As Integer

line = currentPoint.Line
If (line = endPoint.Line) Then
If Not (currentPoint.AtEndOfLine()) Then
currentPoint.Insert(Microsoft.VisualBasic.Constants.vbTab)
End If
Exit Do
End If
currentPoint.Insert(Microsoft.VisualBasic.Constants.vbTab)
currentPoint.LineDown()
currentPoint.StartOfLine()
Loop
End If
End Sub

End Module
That seems to work just fine. But every time I hit TAB, a pop-up balloon would flash from the task tray. After seeing that thing for 10 or so times, I lost my mind. It was so brief I couldn't even read what it said. What to do? Why not add a sleep function into my macro? Here's the snippet:

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub EmacsInsertTab()
'' Somewhere in the body...
Sleep(3000)
End Sub


The pop-up said that a macro was running and that I could kill it from the pop-up. Thanks! That's so useful! My macro takes a fraction of a second, of course I'd like the opportunity to stop it. Anyway, it has the option to dismiss it forever.

I took the sleep out and went on my merry way.