145

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?

ChrisN
  • 16,635
  • 9
  • 57
  • 81
  • 1
    Note to those reading this that are using Visual Studio 2010: If you install the PowerCommands extension from Microsoft, you can have the document formatted automatically when saving the file (turn this feature on via Tools/Options). This will among other things remove unnecessary trailing white space. – steinar Oct 05 '12 at 22:00

16 Answers16

79

CodeMaid is a very popular Visual Studio extension and does this automatically along with other useful cleanups.

I set it to clean up a file on save, which I believe is the default.

DLeh
  • 23,806
  • 16
  • 84
  • 128
arserbin3
  • 6,010
  • 8
  • 36
  • 52
  • 3
    This is the best solution on this page. It's easily configurable, automatically done for you on demand or on save, it integrates nicely with VS, it has lots of other very useful features. Good find arserbin3. – Chris R Jul 20 '12 at 17:24
  • There is no way to turn off full code formatting in this extension, so it's incompatible with, for example, custom indentation styles. – aemxdp Sep 28 '13 at 06:18
  • @Andriy I don't understand what you mean. It follows whatever custom indentation you set in visual studio. Set them in Tools > Options > Text Editor > [Language] > Tabs – arserbin3 Sep 30 '13 at 20:18
  • @arserbin3 I mean, that if you want to format your code like this — http://pastebin.com/uJqBQ1u2 — you're out of luck, because on save it will automatically reformat code like http://pastebin.com/761Lzra7 or something like that, depending on options. And there is no option to turn off indentation rules completely. And without codemaid file reformatting doesn't run automatically, so you can save your file with that kind of indentation. – aemxdp Oct 06 '13 at 14:23
  • @Andriy CodeMaid -should- be formatting code like pastebin.com/uJqBQ1u2 by default. If it's not, you should tweak the VisualStudio settings under Tools>Options>Text Editor>[Language]>Formatting>Indentation. If you want different files of the same language to follow different indentation rules, you may want to consider if that's a good idea, to be non-uniform. – arserbin3 Oct 08 '13 at 17:14
  • @Andriy To turn off CodeMaid formatting for particular file name patterns, you can add them under: CodeMaid > Options > Cleaning > File Types > Exclude > T4 generated code. Example: .*\.Designer\.cs – arserbin3 Oct 08 '13 at 17:17
  • 9
    @arserbin3 I think you're missing Andriy's point. I'm working in a huge open-source project. I don't *own* the code - I'm contributing to the code. I cannot change indentation guidelines of the project. I want VisualStudio to remove trailing whitespaces when I modify some file, and I don't what it to mess with anything else in the file (tabs, spaces, indentation, etc). So far, VisualStudio is *the only* IDE that I've work with that cannot do it. Any other half-baked editor (let alone IDE) can do it. I can't configure CodeMaid to some formatting, because formatting varies. – kliteyn Oct 14 '13 at 07:28
  • Would be awesome if an extension like this was available for Visual Studio Code! But this is perfect for pages within my VS solution that need attention. awesome answer! – klewis Jun 21 '16 at 17:29
  • Visual Studio used to do this - I too was flummoxed when a production CI/CD script broke because "virtual space" was enabled and there were spaces after a slash (continuation character in a bash shell). Virtual space, as near as I remember, would remove trailing spaces. Can't find the setting now, and stumbled on this SO. I also agree with Andriy, quite often I have different projects with different settings (Python is the worst) and it's project breaking to use a standard, as other projects are different. – J. Gwinner Mar 21 '22 at 20:21
79

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*$".

oglester
  • 6,605
  • 8
  • 43
  • 63
30

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.

273K
  • 29,503
  • 10
  • 41
  • 64
25

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

Andreas
  • 5,393
  • 9
  • 44
  • 53
Vyrotek
  • 5,356
  • 5
  • 45
  • 70
12

You can do this easily with these three actions:

  • Ctrl + A (select all text)

  • Edit -> Advanced -> Delete Horizontal Whitespace

  • Edit -> Advanced -> Format Selection

Wait a few seconds and done.

It's Ctrl + Z'able in case something went wrong.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
iPixel
  • 161
  • 1
  • 2
9

Taking elements from all the answers already given, here's the code I ended up with. (I mainly write C++ code, but it's easy to check for different file extensions, as needed.)

Thanks to everyone who contributed!

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

    Try
        fileName = document.Name.ToLower()

        If fileName.EndsWith(".cs") _
        Or fileName.EndsWith(".cpp") _
        Or fileName.EndsWith(".c") _
        Or fileName.EndsWith(".h") Then
            ' Remove trailing whitespace
            result = DTE.Find.FindReplace( _
                vsFindAction.vsFindActionReplaceAll, _
                "{:b}+$", _
                vsFindOptions.vsFindOptionsRegularExpression, _
                String.Empty, _
                vsFindTarget.vsFindTargetFiles, _
                document.FullName, _
                "", _
                vsFindResultsLocation.vsFindResultsNone)

            If result = vsFindResult.vsFindResultReplaced Then
                ' Triggers DocumentEvents_DocumentSaved event again
                document.Save()
            End If
        End If
    Catch ex As Exception
        MsgBox(ex.Message, MsgBoxStyle.OkOnly, "Trim White Space exception")
    End Try
End Sub
ChrisN
  • 16,635
  • 9
  • 57
  • 81
6

I personally love the Trailing Whitespace Visualizer Visual Studio extension which has support back through Visual Studio 2012.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
pwhe23
  • 1,196
  • 1
  • 14
  • 15
4

You can use a macro like described in Removing whitespace and rewriting comments, using regex searches

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jorge Ferreira
  • 96,051
  • 25
  • 122
  • 132
3

I am using VWD 2010 Express where macros are not supported, unfortunately. So I just do copy/paste into Notepad++ top left menu Edit > Blank Operations > Trim Trailing Space there are other related operations available too. Then copy/paste back into Visual Studio.

One can also use NetBeans instead of Notepad++, which has "Remove trailing spaces" under the "Source" menu.

Chris Marisic
  • 32,487
  • 24
  • 164
  • 258
Evgenii
  • 36,389
  • 27
  • 134
  • 170
2

I think that the Jeff Muir version could be a little improved if it 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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
2

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.

easeout
  • 8,665
  • 5
  • 43
  • 51
  • 21
    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 Aug 13 '09 at 22:19
  • 23
    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 Jun 11 '10 at 09:09
  • 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. – easeout Jun 11 '10 at 19:38
  • The problem I see is that VS isn't consistent with white-space. Sometimes it will add white-space just before a new method and sometimes not, this results in a -/+ line in the diff for the removal of the non-whitespace and addition of the whitespace line prior to the method declaration. – Brett Ryan Jul 18 '11 at 12:07
  • 3
    The problem with whitespace and indentation cleanup is that it reduces the effectiveness of source control features such as annotate, and this is a problem that Beyond Compare will not solve. Better to get it right first time. – jammycakes Sep 14 '11 at 15:09
  • The right way to do this is to only clean the lines one has modified. The JetBrains IDEs support this, ReShaper alas seemingly does not :( – miracle2k Sep 16 '12 at 20:29
  • @jammycakes But if it's not right, better to correct it sooner rather than later. – Rob Gilliam Sep 23 '13 at 09:02
  • 5
    @KevinConner It seems a lot more difficult IMO to keep _wrong_ whitespace consistent than to fix it once with a giant whitespace commit at the beginning of the project. Then, anyone whose editor is stupid and broken will know it when they check their diffs before committing. – Dan Bechard Aug 07 '15 at 20:02
0

Building on Dyaus's answer and a regular expression 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. Its 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

For anyone else trying to use this in a Visual Studio 2012 add-in, the regular expression I ended up using is [ \t]+(?=\r?$) (don't forget to escape the backslashes if necessary). I arrived here after several futile attempts to fix the problems with a raw conversion of {:b}+$ failing to match the carriage return.

Michael Urman
  • 15,737
  • 2
  • 28
  • 44
0

I think I have a version of this macro that won't crash VS2010 on refactor, and also won't hang the IDE when saving non-text files. Try this:

Private Sub DocumentEvents_DocumentSaved( _
    ByVal document As EnvDTE.Document) _
    Handles DocumentEvents.DocumentSaved
    ' See if we're saving a text file
    Dim textDocument As EnvDTE.TextDocument = _
        TryCast(document.Object(), EnvDTE.TextDocument)

    If textDocument IsNot Nothing Then
        ' Perform search/replace on the text document directly
        ' Convert tabs to spaces
        Dim convertedTabs = textDocument.ReplacePattern("\t", "    ", _
            vsFindOptions.vsFindOptionsRegularExpression)

        ' Remove trailing whitespace from each line
        Dim removedTrailingWS = textDocument.ReplacePattern(":Zs+$", "", _
            vsFindOptions.vsFindOptionsRegularExpression)

        ' Re-save the document if either replace was successful
        ' (NOTE: Should recurse only once; the searches will fail next time)
        If convertedTabs Or removedTrailingWS Then
            document.Save()
        End If
    End If
End Sub
Julian
  • 11
0

I use ArtisticStyle (C++) to do this and also reformat my code. However, I had to add this as an 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 (for example, multiline function parameters) that I can pay the price of running it manually. The tool is free.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Marcin Gil
  • 68,043
  • 8
  • 59
  • 60
-1

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.

  • 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 Mar 10 '10 at 07:21
-1

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.