views:

4202

answers:

11

Is it possible to configure Visual Studio 2008 to automatically remove whitespace characters at the end of each line when saving a file? There doesn't seem to be a built-in option, so are there any extensions available to do this?

+3  A: 

You can use a macro like this: http://www.codeguru.com/cpp/v-s/devstudio_macros/article.php/c3203/


ASIDE NOTE: I also think that VS is far from Eclipse for example with regard with save actions and plugin integration. But that is a fundamental problem regarding the underlying infrastructure and architecture of VS and has nothing to do with the current question.

smink
Thanks :) I have always hated that!
leppie
A: 

I use ArtisticStyle (C++) to do this and also reformat my code. However I had to add this as external tool and you need to trigger it yourself so you might not like it.

However I find it excellent that I can reformat code in more custom way (eg. multiline function parameters) that I can pay the price of running it manually. The tool is free.

Marcin Gil
+9  A: 

Find/Replacing using Regular Expressions

In the Find and Replace dialog, expand Find Options, check Use, choose Regular expressions

Find What: ":Zs#$"

Replace with: ""

click Replace All

In other editors (a normal Regular Expression parser) ":Zs#$" would be "\s*$".

Greg Ogle
+2  A: 

Before saving you maybe be able to use the auto-format shortcut. CTRL+K+D

Vyrotek
+4  A: 

Unless this is a one-person project, don't do it. It's got to be trivial to diff your local files against your source code repository, and clearing whitespace would change lines you don't need to change. I totally understand; I love to get my whitespace all uniform – but this is something you should give up for the sake of cleaner collaboration.

Kevin Conner
Most good diff tools ignore unimportant differences like trailing white space. If your tool doesn't then get Beyond Compare from http://www.scootersoftware.com/
Jim McKeeth
If everyone in the company/project does it, diffs will be clean. You'll just have to cleanup all whitespace once. Then you have a single commit only fixing whitespace and no whitespaces problems in the future.
ThiefMaster
That's true. But whether that flies will depend on the team. Adding one extra step to everybody's job, or even one extra setting to keep in sync, tends to create unnecessary friction. If the team can change, or if team members get to pick their own IDE, etc. then I suggest you just let the whitespace be. It's not that big of a deal.
Kevin Conner
+2  A: 

@Kevin: Whitespace changes like this tend to hit a lot of lines, but I think reading diffs is easy if you have a diff tool that can ignore whitespace changes.

Merging across branches is the harder operation, but if your source control can do it smoothly, great.

Regardless, make sure you commit this kind of change on its own, with no other changes. This helps a lot in both cases.

I'd also recommend warning your teammates ahead of time, doing it when you aren't hard up against a deadline, and pushing the change to all branches as quickly as possible. Your teammates should sync up their local copies as soon as possible, too.

Jay Bazuzi
+11  A: 

You can create a macro that executes after a save to do this for you.

Add the following into the EnvironmentEvents Module for your macros.

Private saved As Boolean = False
Private Sub DocumentEvents_DocumentSaved(ByVal document As EnvDTE.Document) _
                                         Handles DocumentEvents.DocumentSaved
    If Not saved Then
        Try
            DTE.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, _
                                 "\t", _
                                 vsFindOptions.vsFindOptionsRegularExpression, _
                                 "  ", _
                                 vsFindTarget.vsFindTargetCurrentDocument, , , _
                                 vsFindResultsLocation.vsFindResultsNone)

            ' Remove all the trailing whitespaces.
            DTE.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, _
                                 ":Zs+$", _
                                 vsFindOptions.vsFindOptionsRegularExpression, _
                                 String.Empty, _
                                 vsFindTarget.vsFindTargetCurrentDocument, , , _
                                 vsFindResultsLocation.vsFindResultsNone)

            saved = True
            document.Save()
        Catch ex As Exception
            MsgBox(ex.Message, MsgBoxStyle.OkOnly, "Trim White Space exception")
        End Try
    Else
        saved = False
    End If
End Sub

I've been using this for some time now without any problems. I didn't create the macro, but modified it from the one in ace_guidelines.vsmacros which can be found with a quick google search.

A: 

This is a really good example of how to remove trailing whitespace. There are a few things that I would change based on what I discovered using this macro. First of all, the macro automatically converts tabs to spaces. This is not always desirable and could lead to making things worse for people that love tabs (typically Linux-based). The tab problem is not really the same as the extra whitespace problem anyways. Secondly, the macro assumes only one file is being saved at once. If you save multiple files at once, it will not correctly remove the whitespace. The reason is simple. The current document is considered the document you can see. Third, it does no error checking on the find results. These results can give better intelligence about what to do next. For example, if no whitespace is found and replaced, there is no need to save the file again. In general, I did not like the need for the global flag for being saved or not. It tends to ask for trouble based on unknown states. I suspect the flag had been added solely to prevent an infinite loop.

    Private Sub DocumentEvents_DocumentSaved(ByVal document As EnvDTE.Document) _
                                         Handles DocumentEvents.DocumentSaved
    Dim result As vsFindResult
    'Dim nameresult As String

    Try
        document.Activate()

        ' Remove all the trailing whitespaces.
        result = DTE.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, _
                             ":Zs+$", _
                             vsFindOptions.vsFindOptionsRegularExpression, _
                             String.Empty, _
                             vsFindTarget.vsFindTargetCurrentDocument, , , _
                             vsFindResultsLocation.vsFindResultsNone)

        'nameresult = document.Name & " " & Str$(result)

        'MsgBox(nameresult, , "Filename and result")

        If result = vsFindResult.vsFindResultReplaced Then
            'MsgBox("Document Saved", MsgBoxStyle.OkOnly, "Saved Macro")
            document.Save()
        Else
            'MsgBox("Document Not Saved", MsgBoxStyle.OkOnly, "Saved Macro")
        End If

    Catch ex As Exception
        MsgBox(ex.Message, MsgBoxStyle.OkOnly, "Trim White Space exception")
    End Try

End Sub

I added debug message boxes to help see what was going on. It made it very clear that multiple file save was not working. If you want to play with them, uncomment those lines.

The key difference is using document.Activate() to force the document into the foreground active current document. If the result is 4, that means that the text was replaced. Zero means nothing happened. You will see two saves for every file. The first will replace and the second will do nothing. Potentially there could be trouble if the save cannot write the file but hopefully this event will not get called if that happens.

Before the original script, I was unaware of how the scripting worked in Visual Studio. It is slightly surprising that it uses Visual Basic as the main interface but it works just fine for what it needs to do.

Jeff Muir
One change to this is to support saving and restoring the focus to the window that had focus when the save started. Just save the active document after the Try (using currdoc = DTE.ActiveDocument) and before the document.Activate(). After the save is complete, just make the original document active (currdoc.Activate()). It looks a bit funny when the focus gets switched around during the save but it is better than losing focus to code you were not looking at.
Jeff Muir
A: 

I think that the Jeff Muir version could be a little improved if only trims source code files (in my case C#, but is easy to add more extensions). Also I added a check to ensure that the document window is visible because some situations without that check show me strange errors (LINQ to SQL files '*.dbml', for example).

Private Sub DocumentEvents_DocumentSaved(ByVal document As EnvDTE.Document) Handles DocumentEvents.DocumentSaved
    Dim result As vsFindResult
    Try
        If (document.ActiveWindow Is Nothing) Then
            Return
        End If
        If (document.Name.ToLower().EndsWith(".cs")) Then
            document.Activate()
            result = DTE.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, ":Zs+$", vsFindOptions.vsFindOptionsRegularExpression, String.Empty, vsFindTarget.vsFindTargetCurrentDocument, , , vsFindResultsLocation.vsFindResultsNone)
            If result = vsFindResult.vsFindResultReplaced Then
                document.Save()
            End If
        End If
    Catch ex As Exception
        MsgBox(ex.Message & Chr(13) & "Document: " & document.FullName, MsgBoxStyle.OkOnly, "Trim White Space exception")
    End Try
End Sub

Thanks everybody for your help.

David Abella
A: 

A simple addition is to remove carriage returns during the save.

' Remove all the carriage returns.
result = DTE.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, _
                             "\x000d\x000a", _
                             vsFindOptions.vsFindOptionsRegularExpression, _
                             "\x000a", _
                             vsFindTarget.vsFindTargetCurrentDocument, , , _
                             vsFindResultsLocation.vsFindResultsNone)

The key to this working is changing \x000d\x000a to \x000a. The \x prefix indicates a Unicode pattern. This will automate the process of getting source files ready for Linux systems.

Jeff Muir
A: 

Building on Dyaus's answer and a regex from a connect report, here's a macro that handles save all, doesn't replace tabs with spaces, and doesn't require a static variable. It's possible downside? It seems a little slow, perhaps due to multiple calls to FindReplace.

Private Sub DocumentEvents_DocumentSaved(ByVal document As EnvDTE.Document) _
                                         Handles DocumentEvents.DocumentSaved
    Try
        ' Remove all the trailing whitespaces.
        If vsFindResult.vsFindResultReplaced = DTE.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, _
                             "{:b}+$", _
                             vsFindOptions.vsFindOptionsRegularExpression, _
                             String.Empty, _
                             vsFindTarget.vsFindTargetFiles, _
                             document.FullName, , _
                             vsFindResultsLocation.vsFindResultsNone) Then
            document.Save()
        End If
    Catch ex As Exception
        MsgBox(ex.Message, MsgBoxStyle.OkOnly, "Trim White Space exception")
    End Try
End Sub
Michael Urman