Thursday, March 6, 2008

Getting Ctrl-Right Arrow to respect Camel Case

The normal behaviour for Ctrl-LeftArrow and Ctrl-RightArrow in visual studio is "move left/right one word". This is a useful way of navigating the code quickly, however I often wished that when doing this it would stop between the individual words of an IdentifierWithManyWordsInsideIt. This style of capitalization is known as "CamelCase". I think Eclipse implements their ctrl-arrow movement this way.

The following macros implement this behaviour. You can remap Ctrl-Right and Ctrl-Left to MoveRightCamelCaseWord and MoveLeftCamelCaseWord.

I've also provided macros that mimic the behaviour of shift-ctrl-arrow, which extend the selection by one word.

Sub MoveRightCamelCaseWord()
MoveCamelCase(1, False)
End Sub

Sub MoveLeftCamelCaseWord()
MoveCamelCase(-1, False)
End Sub

Sub ExtendSelectionRightCamelCaseWord()
MoveCamelCase(1, True)
End Sub

Sub ExtendSelectionLeftCamelCaseWord()
'extending the selection to the left doesn't seem to work.
MoveCamelCase(-1, True)

' so instead I'll move to the left, then select the word to the right.
'MoveCamelCase(-1, False)
'MoveCamelCase(1, True)
End Sub

Function IsCharTypeDifferent(ByVal prevChar As Char, ByVal nextChar As Char)

If (Char.IsLetterOrDigit(prevChar) And Not Char.IsLetterOrDigit(nextChar)) Then
Return True
End If

If (Char.IsWhiteSpace(prevChar) And Not Char.IsWhiteSpace(nextChar)) Then
Return True
End If

If (Char.IsPunctuation(prevChar) And Not Char.IsPunctuation(nextChar)) Then
Return True
End If

Return False

End Function

' move the cursor to the left/right by one word, in this case the definition of a 'word' is changed
' to respect CamelCase. moveDirection is -1 or 1 depending on whether we're moving left or right
Sub MoveCamelCase(ByVal moveDirection As Integer, ByVal extend As Boolean)
Dim sel As TextSelection = DTE.ActiveDocument().Selection()
Dim objAnchor As VirtualPoint = sel.AnchorPoint

Dim finished As Boolean

While (Not finished)

Dim selPoint As VirtualPoint
If (moveDirection > 0) Then
selPoint = sel.BottomPoint()
Else
selPoint = sel.TopPoint()
End If


Dim leftChar, rightChar As Char
Dim editPoint As EditPoint

editPoint = selPoint.CreateEditPoint()

' Move the caret to the right by one
If (moveDirection > 1) Then
editPoint.CharRight(moveDirection)
Else
editPoint.CharLeft(-moveDirection)
End If

sel.MoveToPoint(editPoint, extend)

' Get the two characters surrounding the new caret position.
editPoint.CharLeft(1)
leftChar = editPoint.GetText(1)
editPoint.CharRight(1)
rightChar = editPoint.GetText(1)

' Which character out of these two is the 'prev' and 'next' depends on which direction we're going.
Dim prevChar, nextChar As Char
If (moveDirection > 0) Then
prevChar = leftChar
nextChar = rightChar
Else
prevChar = rightChar
nextChar = leftChar
End If

' Keep repeating the loop until we meet the right conditions for stopping
If (IsCharTypeDifferent(prevChar, nextChar)) Then
finished = True
Else
' Stop when the character to the right is uppercase letter and character to left is lower case.
If (Char.IsLetter(leftChar) And Char.IsLetter(rightChar)) Then
If (Char.IsLower(leftChar) And Char.IsUpper(rightChar)) Then
finished = True
End If
End If

End If

End While

End Sub

Wednesday, July 18, 2007

Indent code and surround with braces

This macro saves a bit of time by adding braces around a piece of code and indenting the block -- something I do a dozen times in a normal day.

' Takes the current selection, indents it, and surrounds with braces.
Sub CreateCodeBlock()
CreateCodeBlock_Helper("{", "}")
End Sub

' Takes the current selection, indents it, and surrounds it with startOfBlock and endOfBlock
Sub CreateCodeBlock_Helper(ByVal startOfBlock As String, ByVal endOfBlock As String)
Dim sel As TextSelection = DTE.ActiveDocument.Selection
Dim objAnchor As VirtualPoint = sel.AnchorPoint
Dim bottomPoint As VirtualPoint = sel.BottomPoint

Dim trimmedString As String = sel.Text.TrimStart()
Dim numChars As Integer = sel.Text.Length - trimmedString.Length
Dim spaceString As String = sel.Text.Substring(0, numChars)

objAnchor.CreateEditPoint().Insert(spaceString + startOfBlock + vbCrLf)
bottomPoint.CreateEditPoint().Insert(spaceString + endOfBlock + vbCrLf)
sel.Indent()
'sel.LineUp()


'sel.Text = "{" + vbCrLf + sel.Text + "}" + vbCrLf
End Sub


Once I started to write a whole library of code that was in a namespace, I found it useful to extend the macro so that it indents everything and surrounds it with a "namespace XXX {". The closing brace at the end includes a comment that says "} // end of namespace XXX"

' Takes the current selection and surrounds it with "namespace xxx {" and "}"
' (after prompting user for the namespace name xxx)
Sub SurroundWithNamespace()
Dim name As String
name = InputBox("Namespace name")
CreateCodeBlock_Helper("namespace " + name + vbCrLf + "{", vbCrLf + "} // end of namespace " + name)

End Sub

Close All Annoying Windows -- I just want a monitor full of code!

I find it annoying to have to constantly be closing those windows that appear at the bottom of the screen whenever you do a "Find in files", or the error list window. Mapping this macro to a hot-key is a handy shortcut to avoid having to close these all up by clicking on the "X".

Sub CloseBottomWindows()
DTE.Windows.Item(Constants.vsWindowKindOutput).Close()
DTE.Windows.Item(Constants.vsWindowKindFindResults1).Close()
DTE.Windows.Item(Constants.vsWindowKindTaskList).Close()
End Sub


Some of the windows may not have a "vsWindowKind" constant. In that case, you can close them by finding them by the window title.

' Hide windows that match certain titles
Sub HideOtherBottomWindows()
Dim window As Window
For Each window In DTE.Windows
'MsgBox(window.Caption)
If (window.Caption.Equals("File Finder")) Then
window.Close()
End If
If (window.Caption.Contains("Code Definition Window")) Then
window.Close()
End If
If (window.Caption.Contains("Error List")) Then
window.Close()
End If
If (window.Caption.Equals("VA Find References Results")) Then
window.Close()
End If
Next
End Sub

Adding a CodeWarrior breakpoint from inside Visual Studio


  • This macro uses CodeWarrior's COM interface to allow you to set a breakpoint from within Visual Studio

  • If you work exclusively on GameCube or PS2, you might want to map this macro to F9



Sub CWSetBreakpointHere()
Dim objSel As TextSelection = DTE.ActiveDocument.Selection
Dim objAnchor As VirtualPoint = objSel.AnchorPoint
Dim line As Long = objAnchor.Line
Dim fileName As String = DTE.ActiveDocument.FullName()

Dim CW = CreateObject("CodeWarrior.CodeWarriorApp")
CW.Debugger.SetBreakpointInSource(fileName, line, 0)

DTE.ExecuteCommand("Debug.ToggleBreakpoint")
MsgBox("Breakpoint set in CodeWarrior: " + vbCrLf + fileName.ToString() + ":" + line.ToString())
End Sub


Once I started working on a mix of GameCube and PC project files, I found it useful to map this next macro to F9. It looks at the name of the Visual Studio Project to determine whether to use the above CWSetBreakpointHere macro, or the normal Debug.ToggleBreakpoint command.

' Sets a breakpoint through CodeWarrior if the project name ends with wii or gamecube.
Sub MyToggleBreakpoint()
Dim ProjectName As String = ActiveSolutionProjects.GetValue(0).Name
If (ProjectName.ToLower().EndsWith("gamecube") Or ProjectName.ToLower().EndsWith("wii")) Then
CWSetBreakpointHere()
Else
DTE.ExecuteCommand("Debug.ToggleBreakpoint")
End If
End Sub