Fixing Visual Studio undo behavior with assembly editing
Visual Studio has a really annoying behavior regarding its undo/redo functionality.
In addition to text edits, it somehow also considers expanding/collapsing code blocks as an edit step.
So if you undo a change and then happen to expand a section you cannot redo it anymore.
And because searching text or using the go-to-definition function can lead to accidental automatic
expansions this is rather dangerous.
I have no idea who could have thought that would be a good idea but it’s been in there ever since VS2013.
Solution!
The Visual Studio editor is a .NET executable which means it is fairly easy to modify even with just the tools ildasm/ilasm available from Microsoft.
But by using the userfriendly debugger and assembly editor dnSpy modifications like this becomes a breeze.
So we’re going to fix this issue directly in the code which resides inside Microsoft.VisualStudio.Platform.VSEditor.dll
!
Steps
-
Get dnSpy from the GitHub releases page
You might need to install the .NET Framework 4.6.2 redistributable.
You can also try to removetag from ‘dnSpy.exe.config’ and ‘dnSpy-x86.exe.config’ to run it anyway. -
Close all Visual Studio editor instances
-
Open
Microsoft.VisualStudio.Platform.VSEditor.dll
in dnSpy from one of the places below (depending on Visual Studio version):
2017:C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\Editor
2015:C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\Editor
2013:C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\Editor
-
Browse to the function
Microsoft.VisualStudio.Text.Outlining.UndoManager.Implementation
->OutliningUndoManagerFactory
->TextViewCreated
-
Right-click the method name and select ‘Edit Method (C#)…’
- Remove the whole body of the function, so it looks like this:
// Microsoft.VisualStudio.Text.Outlining.UndoManager.Implementation.OutliningUndoManagerFactory public void TextViewCreated(IWpfTextView textView) { }
-
Make sure you save a backup of the original ‘Microsoft.VisualStudio.Platform.VSEditor.dll’ somewhere!
-
Use ‘File’ -> ‘Save Module …’ in dnSpy and overwrite the dll with the patched version
-
Next you might need to clear the version stored in the Windows global assembly cache.
Just delete the directoryC:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.VisualStudio.Platform.VSEditor
if it exists.
It does not seem to exist for Visual Studio 2017 on the machine I tested. - Finally you need to refresh the extension cache and configuration by running the following two commands in the command prompt:
# For Visual Studio 2017: "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.exe" /updateconfiguration` "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.exe" /clearcache` # For Visual Studio 2015: "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" /updateconfiguration` "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" /clearcache` # For Visual Studio 2013: "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe" /updateconfiguration` "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe" /clearcache`
- Launch Visual Studio and enjoy a better experience!
Details
What the change in OutliningUndoManagerFactory.TextViewCreated
did was to avoid the
creation of the OutliningUndoManager
whose sole purpose is to generate undo transactions
for outline collapsing and expanding. Without it we get what we want:
Outlining still works fine but doesn’t interfere with the undo/redo buffer anymore.
I found out about it by using the debugger feature of dnSpy. After attaching to a running
Visual Studio instance I loaded all modules with interesting sounding names (text, editor, etc.)
and then used the search function to find undo related functions. Next I added breakpoints to
functions which looked promising. Turns out undo gets pushed in VsUndoTransaction.AddUndo
inside Microsoft.VisualStudio.Editor.Implementation.dll
. After triggering the breakpoint
my target was just one callstack jump away.
At first I wanted a solution that did not require a modification of the binary.
The function EditorOptions.IsOutliningUndoEnabled()
looked promising but it turned out to
be an option controlled by the state the editor is in and not something a configuration file or
setting can affect.
Thus I went ahead with a code modification and it was just a matter of finding the easiest
way which turned out to be the one in OutliningUndoManagerFactory
. Modifying the mentioned
IsOutliningUndoEnabled()
to always return false would work just as well I think.
Further fixes
The separate outline feature “Hide Selection” also messes with the undo buffer.
One could fix that as well by removing the generation of undo transactions inside
Outlining.AdhocOutliner
of Microsoft.VisualStudio.Editor.Implementation.dll
.
Conclusion
It is kind of astonishing to think that an end-user has such an easy and powerful way to customize closed
source applications, even as large as Visual Studio. And it requiring just a single, fairly small tool
like dnSpy makes it even more interesting. Obviously hats off to
0xd4d and the rest of its contributors.
But also thanks has to go out to Microsoft for keeping the .NET il fairly open and also for not pushing
for more code obfuscation. This certainly would not be as easy if the Visual Studio binaries were
obfuscated or otherwise encrypted.
Although during my tests I encountered a non-updated VS2017 version which did not want to run with a
modified editor dll. Maybe there are checks against modifications in place but not fully active.