Friday, September 11, 2009

How to Comment-Uncomment Code Selection in Style Sheets - (or How to fix Visual Studio's broken implementation)

If you commonly comment out portions of code, then you are used to highlighting a portion of text and using the shortcut of CTRL+K, CTRL+C (or the toolbar) to comment out the selected text.

While this will work for most source files, it does not currently work inside Visual Studio for style sheets (.css) files. (See screenshot and note the status bar message)

Comment Selection not currently available

Why Microsoft has left out this basic functionality is beyond me; In this post, I would like to demonstrate how you can write your own macro to fix it.

First we will record a basic macro - later we will modify it to suit our needs.

record temporary macro

Start out by opening a style sheet and placing your cursor at the relevant piece of code, next start recording by selecting Record TemporaryMacro (Tools-->Macros-->Record... or CTRL+Shift+R). Using the keyboard highlight some style information and Ctrl+X to cut your selection to the clipboard. Next add the beginning of our comment /* and paste your code back in. Lastly add the comment close */ and stop recording your macro.

temporarymacro

Browse your Macro Explorer and you will notice a "RecordingModule" and if expanded a "Temporary Macro". Right click on the macro and choose edit causing your Macro Editor to open. If you completed the steps outlined above you should have ended up with something roughly similar to this:

Sub TemporaryMacro()
DTE.ActiveDocument.Selection.LineUp(True, 5)
DTE.ActiveDocument.Selection.StartOfLine(vsStartOfLineOptions.vsStartOfLineOptionsFirstText, True)
DTE.ActiveDocument.Selection.Cut()
DTE.ActiveDocument.Selection.Text = "/*"
DTE.ActiveDocument.Selection.Paste()
DTE.ActiveDocument.Selection.Text = "*/"
End Sub

You can see that it recorded the exact steps I took: I highlighted five lines, cut the text, typed, pasted and finally typed some more. If you go back to your text editor and undo your changes and run the macro you should end up with the same ending result.

commented style - css

As you can see, for repeatable actions, Macros are great - however at this point you might be thinking - What if I want to comment out 3 lines (or 10 or more)?. Now that we have our starting point, we can modify our temporary macro to better accommodate our concerns and match the common VS implementation.

Let's start modifying by simply grabbing the currently selected text, as you can see VS stores this in DTE.ActiveDocument.Selection and then we will append /* and */ to the beginning and end.

So we end up with something like this:

Sub TemporaryMacro()
Dim txtSel As TextSelection
txtSel = DTE.ActiveDocument.Selection
txtSel.Text = "/*" + txtSel.Text + "*/"
End Sub

That's a fairly good substitute for our previous recorded macro; We can highlight lines with our mouse or keyboard and execute the macro, and it will comment out a variable number of lines. It works, but could be better.

With the above variation, if you highlight multiple lines and then 'undo' the changes you will notice that it will undo it line by line, a minor annoyance, but luckily the macro system has something just for this scenario. It is called the UndoContext and is fairly easy to use:

Sub TemporaryMacro()
Try
DTE.UndoContext.Open("Comment CSS")
Dim txtSel As TextSelection
txtSel = DTE.ActiveDocument.Selection
txtSel.Text = "/*" + txtSel.Text + "*/"
Finally
DTE.UndoContext.Close()
End Try
End Sub

You should wrap it in a Try/Finally - This is important, but I won't go into 'why' here. Now highlight multiple lines run the macro and then undo it, your changes are preformed as a single transaction and rolled back as one too. It is a nice little enhancement that will give your final macro some polish.

One final enhancement to our macro, In VS you can Comment and also Uncomment a section of code - that would be a nice feature too. However, instead of writing it as a separate macro, let's detect if the code is currently commented, and if so, uncomment it. In the end we should be able to bind a single shortcut for both comment and uncomment.

Here is our final code:

Sub CommentCSS()
'Detect if in a CSS file
If Not DTE.ActiveDocument.Name.EndsWith("css") Then Return
Try
DTE.UndoContext.Open("Comment CSS")

Dim txtSel As TextSelection = DTE.ActiveDocument.Selection
Dim currText As String = txtSel.Text

If currText.Length > 0 Then
Dim newTxt As String
If currText.Trim.StartsWith("/*") AndAlso currText.Trim.EndsWith("*/") Then
newTxt = currText.Replace("/*", "").Replace("*/", "")
Else
newTxt = "/*" + currText + "*/"
End If

txtSel.Delete() 'This is to help keep formatting correct when multiline
txtSel.Insert(newTxt, vsInsertFlags.vsInsertFlagsInsertAtEnd)
End If
Finally
DTE.UndoContext.Close()
End Try
End Sub

Now to bind our Macro to a keyboard chord: Go to Tools-->Options-->Environment-->Keyboard - In the show commands type CommentCSS, in the dropdown "Use new shortcut in" change the selection to CSS Source Editor, finally in the "Press Shortcut Keys" type CTRL+K, CTRL+C and assign it.

key binding

Tuesday, September 8, 2009

Quickly Reformat your Project Files

Recently on twitter Phil Haacked asked about reformatting the files in a project where it didn't match his preferences. We have all been there, looking at a file that we downloaded or was given, and would find it easier to follow if we could reformat it.

There is an often overlooked feature of Visual Studio that will reformat our code for us. I frequently use it on HTML documents to quickly restructure code in View Source.

This functionality can be found under Edit-->Advanced-->Format Document (or CTRL+K, CTRL+D) as well as Format Selection – which only applies to the currently highlighted text.

image

One thing to mention is that you should define how you want your code formatted by going to Tools-->Options-->Text Editor and then select your language of choice. (Some languages offer a greater degree of customization)

Now that you know how to reformat a document and how you can define its result, we will write a macro that will achieve what Phil was looking for by - loop through each source file - open, format, save, and then close it:

    Sub FormatAll()
For Each proj As Project In DTE.Solution.Projects
FormatFileRecur(proj.ProjectItems())
Next
End Sub


Sub FormatFileRecur(ByVal projectItems As EnvDTE.ProjectItems)
For Each pi As EnvDTE.ProjectItem In projectItems
If pi.Collection Is projectItems Then
Dim pi2 As EnvDTE.ProjectItems = pi.ProjectItems
Try
If Not pi.IsOpen Then pi.Open(Constants.vsViewKindCode)
pi.Document.Activate()
DTE.ExecuteCommand("Edit.FormatDocument")
If Not pi.Document.Saved Then pi.Document.Save()
pi.Document.Close()
Catch ex As Exception
'Ignore this error - some project items cannot be opened.
End Try
If pi2 IsNot Nothing Then
FormatFileRecur(pi2)
End If
End If
Next
End Sub