When working on large projects, I frequently find myself jumping around to various locations in numerous files (I use the Go To Definition context menu command all the time). Once I have navigated down a particular path I want to get back to my original starting position. While I could use bookmarks to mark my place before I leave to chase something down, I tend to use bookmarks for other purposes (plus bookmarks only work in the context of the containing source file - you can't Edit.NextBookmark between source files). It has been pointed out to me that Visual Studio 7 has the command Navigate Backward which will do this, however, I find that it has some quirks that I don't like.
This add-in provides a more convenient Navigate Backward mechanism. It monitors navigations away from the current document position and provides a means to go back to the previous position or to any position in a chain of navigations. I'm defining a document position as the document (i.e., source code file) file name, and the line and column number of the text insertion cursor in that document.
Visual Studio provides a fast mechanism to jump around in the code with the Go To Definition and Go To Reference context menu commands. There are also a number of other commands that can change the current active editing location in a source file (e.g., Edit.DocumentEnd, Edit.NextBookmark, Edit.GotoNextLocation, etc.). In Visual Basic 6, there was a "Previous Location" context menu command that could be selected to return you right back to where you had been. Unfortunately, the editing context menu in Visual Studio 2002 and 2003 (and the 2005 Beta) do not include that support. There is an Edit.GoToPreviousLocation command defined, but unfortunately that's used for moving to a previously marked location (e.g., from a Find in Files text search).
The Navigate Backward command is the replacement for the "Previous Location" command in VB6. It is an improvement, but has several quirks (that I don't like). Specifically:
- It is not available on the editing context menu (so it's either move the mouse pointer up to the top of the VS window to the standard toolbar or take your hand off the mouse to type 'Ctrl+-'), which is inconvenient.
- Every text insertion point is recorded, so the navigation history gets rather long as you click around in a source file (which I don't really consider all that helpful - I want the major moves, not every little minor move).
- When code navigation opens a source file and then you Navigate Backward, you have to select that command twice to get back to where you really were. This is a consequence of recording every text insertion change (when a new file is opened, the beginning of the file is recorded as a navigation location even though visually you see some other location).
- A file that was opened due to a navigation is left open, when you return from it (navigate backward).
Once you have loaded the add-in, the context menu in the code window is modified to add two additional commands -
Previous Location and
Choose Location .... These menu items are enabled based on your code navigation actions. My preference is to have consistent menus, so the menu items are enabled/disabled rather than to have them appear/disappear as a function of command availability. NOTE: If you do not set the option to load the add-in at startup, the menu states will appear as enabled. When you select one of those commands, the add-in gets loaded and Visual Studio will do a
QueryStatus check on each command and then they will correctly show as being disabled.
No navigation history exists.
A single navigation location exists.
Two or more navigation locations exist. Go back to the most recent location or choose from a list.
Previous Location is enabled and then selected, the current edit position is restored back to a remembered position. The document being left will be closed if it was opened as a result of code navigation (and then not modified). My preference is to only have documents open that I'm currently working with and I consider it a convenience to automatically close a document that was only opened up for a "reference" check.
Choose Location... is enabled and then selected, the
PositionDialog form will be displayed (see the picture below). It shows the history of document locations that have been left due to a navigation command. The most recent location is shown first. Clicking on any row results in a selection and a return to that position.
PositionDialog also allows you to clear the recorded position history. The "Close Skipped Over Documents" check box determines if any documents that were opened along the way get closed automatically on the way back.
The skeleton of the add-in was created by the Visual Studio add-in project wizard (specifically the
Connect class). There are many articles on how to write an add-in for Visual Studio, so I won't attempt to go over those basics here.
Initially, I thought I could simply use a handler for the Text Editor
LineChanged event. The event handler is passed a value indicating the nature of the change (see the MSDN table of values). The
vsTextChangedCaretMoved value (the insertion point was moved) suggested to me that this would work nicely. However, the
LineChanged event only fires when the text of the line has been changed and you move away from that line. It's probably just as well that this doesn't work the way I hoped - it would end up with a lot of events firing and a navigation history just like the Navigate Backward command.
Reading around news group postings, I found someone else who was trying to tackle a similar problem and was able to get around it by putting in place
AfterExecute handlers for certain commands. That's the approach I settled on, and for the most part it works out quite well. In the
OnConnection method, I iterate through a list of editor commands that I want to be notified when they are executed. For each command I get its corresponding
EnvDTE.CommandEvents and register a
for( int i = 0; i < _interceptCommandNames.Length; i++ )
EnvDTE.Command cmd = _vsNet.Commands.Item( _interceptCommandNames[i], -1 );
if( null != cmd )
_commandEvents[i] = _vsNet.Events.get_CommandEvents( cmd.Guid, cmd.ID );
new _dispCommandEvents_BeforeExecuteEventHandler( BeforeExecute );
new _dispCommandEvents_AfterExecuteEventHandler( AfterExecute );
Initially I just used the
BeforeExecute handler which just simply recorded the current document position. However there are situations where code navigation is requested but no actual navigation occurs. So the
AfterExecute handler is used to verify that, the current document position has changed before the cached position location is recorded. The
PositionManager class (the
_pm instance variable in the code below) does all the handling of document positions, which are stored internally using
System.Collections.Stack. It in turn relies on the
DocumentPosition data class which encapsulates the position information.
private void BeforeExecute( string Guid,
ref bool CancelDefault)
CancelDefault = false;
catch( Exception ex )
DisplayInOutputWindow( ex.ToString() );
private void AfterExecute( string Guid,
object CustomOut )
if( _pm.PositionHasChanged )
catch( Exception ex )
DisplayInOutputWindow( ex.ToString() );
PositionManager.RecordCachedPosition method performs the check to see if the current document location required the opening of a document. If so, it sets the
OpenedDocumentName property so that document can be closed on a return.
public void CacheCurrentPosition()
_position = GetCurrentPosition();
_openedDocumentCount = _vsNet.Documents.Count;
public void RecordCachedPosition()
if( _openedDocumentCount != _vsNet.Documents.Count )
DocumentPosition newPosition = GetCurrentPosition();
_position.OpenedDocumentName = newPosition.DocumentName;
_positionStack.Push( _position );
A selection for either of the
Previous Location and
Choose Location ... commands results in a call to the
Connect.Exec method which has to make a decision as to which command is actually being processed and then act accordingly. The relevant code from the method is shown below.
bool closeOnReturn = true;
DocumentPosition position = null;
if( COMMAND_PREVIOUS_LOCATION == commandName )
position = _pm.PreviousPosition;
if( COMMAND_CHOOSE_LOCATION == commandName )
PositionDialog pd = new PositionDialog();
pd.InitializeForm( _pm );
if( DialogResult.OK == pd.ShowDialog() )
position = pd.SelectedPosition;
closeOnReturn = pd.CloseSkippedDocuments;
if( null != position )
MoveToPosition( position, closeOnReturn );
handled = true;
Connect.MoveToPosition method is pretty straight forward. It handles the closing of a document on return. The only trick has to do with calling
MoveToDisplayColumn with the line and column numbers. This results in the cursor being placed in the correct location (otherwise you end up with the column number being treated as the character count from the beginning of the line).
private void MoveToPosition( DocumentPosition position, bool closeDocument )
if( closeDocument && ( 0 < position.OpenedDocumentName.Length ) )
Document leavingDoc = _vsNet.Documents.Item( position.OpenedDocumentName );
if( ( null != leavingDoc ) && leavingDoc.Saved )
leavingDoc.Close( EnvDTE.vsSaveChanges.vsSaveChangesPrompt );
Document doc = _vsNet.Documents.Item( position.DocumentName );
if( null != doc )
TextSelection ts = (TextSelection)_vsNet.ActiveDocument.Selection;
There are code navigation mechanisms in Visual Studio whose associated event is not currently captured. Should you navigate away from your current edit position using one of those mechanisms, there is no intercept event mechanism to capture the current location before moving to the desired position (and therefore this add-in will not be able to return you back to your starting position). This can result in a misleading situation where this add-in's menu items have been enabled by an earlier code navigation event and selecting
PreviousPosition would put you back to an earlier document position and not the one you thought you were going back to.
Specifically, the navigation events not (currently) captured are:
- Clicking on the "Find Next" button of the Find dialog (while the
Edit.FindNext command event is captured, that command doesn't seem to be executed with the button click).
- Selecting a name in the Member Definition combo box.
- Clicking on a find window result or a task list item.
I found that developing a VS.NET add-in user interface is really inconvenient (specifically the toolbar commands - the dialog form was no different than a regular WinForm application). The add-in's user interface controls are created once (during add-in setup) which I was unable to set a breakpoint for. Once created, there is no convenient mechanism to then remove those toolbar buttons. What I ended up having to do was:
- Use the Add-in Manager and unload the add-in and exit Visual Studio.
- Delete the add-in DLL file.
- Restart Visual Studio and click on (each of) the command button(s) on which the add-in is installed. Visual Studio will complain about the add-in not functioning and asks if you want to remove the add-in (click on OK).
- Re-create the add-in's registry key and values. (HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AddIns\GoBackAddin.Connect for this add-in) and then re-run the ReCreateCommands.reg file created by Visual Studio.
- Restart Visual Studio and load the add-in through the Add-in Manager
- When that doesn't work, use the command devenv /setup in a command window.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.