VSHelper - Visual Studio IDE enhancements
from:http://www.codeproject.com/macro/VSHelper.asp
- Download source code - 49 Kb
- Download binaries for Visual Studio 2003 - 811 Kb
- Download binaries for Visual Studio 2005 - 2,239 Kb
Introduction
When I had to switch from Visual C++ 6 to .NET, I discovered that things were not working the same way as they used to. I had to adapt to the new IDE. I thought, why can't the IDE adapt to me? That's how the VSHelper add-in was born. It fixed some of the pet peeves I had with .NET IDE. Over time I continued to add more functionality. Hopefully, other people will find some of these features useful.
The add-in registers 11 new commands, which you can map to key combinations or access from the toolbar.
Add-in commands
Cut and copy (use plain text, do nothing if no text is selected)
These are replacements for the standard Edit.Cut
and Edit.Copy
commands. The difference is that all special formatting is stripped out from the clipboard. What does that mean?
Imagine, you copy this text from your C++ source to your email:
The text is copied in Rich Text Format, which preserves the fonts and colors of the source window. Not all applications interpret the RTF content correctly. Even if they do, you may still prefer plain text.
The text pasted in Outlook Express (the font and the background colors are not preserved):
The text pasted in Word (the font is not preserved properly):
The text pasted in WordPad (technically correct, but still not the same as the original):
Another feature of the new Cut
and Copy
is that they do nothing if no text is selected. The original Edit.Cut
and Edit.Copy
put the current line in the clipboard even if nothing is selected. If you press Ctrl+C when you actually wanted to press Ctrl+V, the editor will copy the current line and mess up the clipboard contents.
If for some reason you want to preserve the full RTF content, in that case, you can access the Edit.Copy
command from the main menu, or from the right-click menu.
Esc (close all secondary windows)
Do you remember how you could press Esc in VC6 and hide all the unnecessary windows? It is an incredibly useful way to quickly maximize your work area. Sadly, .NET doesn't support that. And there is an even greater need for it because there are more modeless windows that pop up and they don't close on their own - Properties, Find window, Toolbox, etc.
The new Esc
command can help in this situation. Pressing Esc several times will progressively:
- focus the main document,
- clear the text selection,
- close the unnecessary windows.
The list of affected windows can be customized from the settings.
The initial selection includes: Output window, Find Results windows, Properties, Task List, Toolbox, Find Symbol, Find and Replace, Visual Assist's Find References and the Incredibuild window.
The list of all available windows is retrieved from the HKLM\SOFTWARE\Microsoft\VisualStudio\7.1\ToolWindows registry key. For VS 2005 replace 7.1 with 8.0.
FindNext and FindPrev (Find the text and close the Find window)
In VS 2003, the Find window is no longer modal. This is not a big deal in itself; the problem is that it loses focus very easily. For example, press Ctrl+F, type some text, and then press F3. The Find window stays open, but loses focus. Now, you have this modeless, focus-less window obscuring your editor until you press Cltr+F and Esc, or reach for the mouse to close it.
The new FindNext
and FindPrev
commands first invoke the original Edit.FindNext
and Edit.FindPrevious
commands and then close the Find window. It works like this:
ExecuteCommand(next?"Edit.FindNext":"Edit.FindPrevious"); CloseWindow(wins,vsWindowKindFindReplace);
Output (show the Output and Find Results windows)
In VC6, the Output and Find Results are part of the same window. You can have a single key combination to go to them. In .NET, they are separate windows and you'll need different shortcuts for each of them. The new Output
command will show all the three with a single key combination. If the windows are docked together the Output window will be on top, then the Find Results 1, and then the Find Results 2. Here's how it's done:
CComPtr<Window> output=FindWindow(wins,vsWindowKindOutput); CComPtr<Window> find1=FindWindow(wins,vsWindowKindFindResults1); CComPtr<Window> find2=FindWindow(wins,vsWindowKindFindResults2); find2->Activate(); find1->Activate(); output->Activate();
Opposite (switch between C/CPP and H files)
This command simply switches between the source and header files:
// Retrieve the current document's name CComPtr<Document> doc; m_pDTE->get_ActiveDocument(&doc); BSTR name; doc->get_FullName(&name); wchar_t name2[_MAX_PATH+_MAX_EXT]; memcpy(name2,name,wcslen(name)*2+2); // modify name2 according to the settings // ..... // Open the new file name BSTR name3=SysAllocString(name2); Window *win; BSTR kind=_com_util::ConvertStringToBSTR(vsViewKindTextView); CComPtr<ItemOperations> operations; m_pDTE->get_ItemOperations(&operations); operations->OpenFile(name3,kind,&win);
The Opposite
command can cycle between a list of extensions. By default it is .h -> .inl -> .cpp -> .c -> .h
. This can be changed from the settings.
Also if the current file starts with // ## <filename>
the command will switch to the specified file name. For example to toggle between File.inc and File.hpp you do:
Put this in the beginning of File.inc:
// ## File.hpp
..........
Put this in the beginning of File.hpp:
// File.inc
..........
The "// ##" prefix is configurable from the settings.
Locate (find document in the Solution Explorer)
Visual Studio has an option "Track Active Item", which will always expand the tree in Solution Explorer to show the current document. This can be very annoying when working with large projects. After some time, all the projects and their folders become expanded, and it takes lot of scrolling to find the files you are looking for. In VS2002, this was the only behavior. In VS2003, it is optional, and turned off by default (thanks, MS :)).
But occasionally you do need to find the current file in the Solution Explorer. That's when the Locate
command comes in. It will locate the current document in the Solution Explorer. It will switch focus to Solution Explorer and will scroll the selected item into the view. The code uses a combination of VS automation API and Win32 API:
// Find the ProjectItem for the current document CComPtr<Document> doc; m_pDTE->get_ActiveDocument(&doc); CComPtr<ProjectItem> pitem; doc->get_ProjectItem(&pitem); // Find the Solution Explorer object CComPtr<Windows> wins; m_pDTE->get_Windows(&wins); CComPtr<Window> se=FindWindow(wins,vsWindowKindSolutionExplorer); // Get the UIHierarchyItems CComPtr<UIHierarchy> uih; se->get_Object((IDispatch**)&uih); CComPtr<UIHierarchyItems> items; uih->get_UIHierarchyItems(&items); // Convert the ProjectItem into UIHierarchyItem CComPtr<UIHierarchyItem> hitem=FindProjectItem(items,pitem); if (hitem) { // Select the item in the Explorer hitem->Select(vsUISelectionTypeSelect); if (g_SolutionTree) { // Scroll the item into view HTREEITEM h=TreeView_GetSelection(g_SolutionTree); TreeView_EnsureVisible(g_SolutionTree,h); } // Activate the Solution Explorer window se->Activate(); }
Home (go to the first column, in VS 2003 version only)
Another thing missing in VS 2003 is the "Go to the first column of the line" command. The new Home
command does exactly that – jumps to the first column of the current line. That command exists only in VS 2003 version of the add-in because in VS 2005 that feature is back - the command is called Edit.LineFirstColumn
.
CComPtr<Window> w; m_pDTE->get_ActiveWindow(&w); // Get the selection CComPtr<TextSelection> sel; w->get_Selection((IDispatch **)&sel); // Jump to the beginning of the line long top; sel->get_TopLine(&top); sel->GotoLine(top,FALSE);
ClearBookmarks (in VS2005 version only)
Starting with VS2005 if you try to clear all bookmarks in the document you will be asked "Are you sure you want to delete all of the bookmark(s)?". It would be great if there was a way to turn this question off after the first time. But there isn't. You can use the ClearBookmarks
command instead:
CComPtr<Document> doc; m_pDTE->get_ActiveDocument(&doc); // get the text document CComPtr<IDispatch> spTextDocumentDisp; doc->Object(CComBSTR("TextDocument"),&spTextDocumentDisp); CComQIPtr<TextDocument> textDoc=spTextDocumentDisp; textDoc->ClearBookmarks();
Settings
The Settings
command opens a dialog box to edit the settings:
Evaluate g_DebugData - Evaluates g_DebugData
when the debugger stops. See below. The change will take effect the next time Visual Studio is started.
Add new files to Perforce - When new files are added to the project they will be added to Perforce automatically. See below. The change will take effect the next time Visual Studio is started.
Always show selection in Solution Explorer - Shows the selection in the Solution Explorer even when it is inactive. See below. This option is only available in Visual Studio 2005. The change will take effect the next time Visual Studio is started.
Support for Numpad Clear key - Translates VK_CLEAR
to VK_NUMPAD5
. See below. This option is only available in Visual Studio 2005. Turning this option ON requires restarting of Visual Studio.
Opposite order - lists the order of extensions for the Opposite
command. You can add or remove extensions and change their order.
Opposite prefix - the prefix for the Opposite
command.
Windows to close - shows a list of all tool windows in Visual Studio. The selected windows will be closed with the Esc
command.
Additional features
Subclassing the Solution Explorer (in VS 2005 version only)
The developers of VS 2005 decided that the selection in the Solution Explorer should be hidden if the window doesn't have focus and if "Track Active Item" is off. I have that option off (see the Locate
command). Now imagine the following situation: You select multiple projects and open their settings. The Solution Explorer loses focus (because the settings dialog gets the focus), and now you can't see which projects you are editing! I'm sure there must be a good reason behind that behavior, I just can't find it.
Fortunately, the Solution Explorer is using a standard TreeView control. The add-in subclasses the Solution Explorer to force the TVS_SHOWSELALWAYS
style:
LRESULT CALLBACK CConnect::SolutionProc( HWND hWnd,
UINT uMsg, WPARAM wParam, LPARAM lParam )
{
// Force the TVS_SHOWSELALWAYS style
if (uMsg==WM_STYLECHANGING && wParam==GWL_STYLE)
((STYLESTRUCT*)lParam)->styleNew|=TVS_SHOWSELALWAYS;
return CallWindowProc(g_OldSolutionProc,hWnd,uMsg,wParam,lParam);
}
Support for Numpad Clear key (in VS2005 version only)
In VC6 IDE you can create shortcuts using the key in the middle of the Numpad (where the digit 5 is). The shortcut can be used with the NumLock turned off. VC6 calls this key "Numpad Clear" - for example "Ctrl+Numpad Clear". VS 2003 still supports such shortcuts, but doesn't have a name for the key - the shortcuts show up as "Ctrl+".
VS 2005 completely ignores the Clear key and it can't be used in shortcuts. On the other hand it accepts the "Num 5" key, which is the same but with NumLock turned on. Since I keep my NumLock turned off, I have a whole key on my keyboard that I can't use.
The VSHelper addin solves that problem by installing a keyboard hook and translating VK_CLEAR
to VK_NUMPAD5
. When the hook is in effect you can press the Clear key with or without NumLock and Visual Studio will accept it as the "Num 5" key:
LRESULT CALLBACK CConnect::NumKeyboardProc( int code, WPARAM wParam, LPARAM lParam ) { if (code<0) return CallNextHookEx(g_KbdHook,code,wParam,lParam); if (CallNextHookEx(g_KbdHook,code,wParam,lParam)) return TRUE; if (!g_bTranslateNum5 || wParam!=VK_CLEAR) return FALSE; if (GetKeyState(VK_CONTROL)>=0 && GetKeyState(VK_SHIFT)>=0 && GetKeyState(VK_MENU)>=0) return FALSE; keybd_event(VK_NUMPAD5,0,(lParam&0x80000000)?KEYEVENTF_KEYUP:0,0); return TRUE; }
The hook is controlled by the "Support for Numpad Clear key" option. If the option is on when Visual Studio starts the hook will be installed. If you really need to enter the Clear key you can temporarily disable the VK_CLEAR -> VK_NUMPAD5
translation by turning off the option. There is only one place in VS where this might be necessary - when creating an accelerator in your resources that uses the Clear key. Even then you can simply type VK_CLEAR in the accelerator table.
Evaluate debug data
The add-in registers an OnEnterBreakMode
event handler. Every time the debugger breaks the handler will calculate the expression ((int)&g_DebugData),d
and will store it in a variable. You can retrieve the value by calling the GetDebugData
function. The purpose of that value will be revealed in another article [1]. :)
static char g_DebugData[128]; // OnEnterBreakMode handler STDMETHODIMP CConnect::OnEnterBreakMode(EnvDTE::dbgEventReason Reason, EnvDTE::dbgExecutionAction* ExecutionAction) { *ExecutionAction=dbgExecutionActionDefault; Expression *exp=NULL; memcpy(g_DebugData,"err",4); if (g_pDebugger) { // Evaluate the expression and store the // result in g_DebugData g_pDebugger->GetExpression(CComBSTR("((int)&g_DebugData),d"), VARIANT_FALSE,-1,&exp); if (!exp) return S_OK; BSTR val=NULL; exp->get_Value(&val); exp->Release(); if (!val) return S_OK; WideCharToMultiByte(CP_ACP,0,val,-1, g_DebugData,sizeof(g_DebugData),NULL,NULL); SysFreeString(val); } return S_OK; }
Perforce helper
When working with Perforce I prefer to use the P4Win client to submit my changes instead of the Visual Studio integration. But sometimes I add files to the project and forget to add them to Perforce. So the next day when somebody tries to build my code they complain about missing files.
For VS 2005 the addin registers OnItemAdded
event handler that fires when a new file is added to the project. For VS 2003 there is no such universal event. Instead, the addin first registers an OnOpened
event handler that fires when a new solution is opened. Then it goes through all projects of the solution and if it finds a C++ project it registers OnItemAdded
handler for that project only. For more details on how to hook project events see here: [2].
Once the OnItemAdded
event is detected the add-in checks if the new file is under Perforce control by issuing the "fstat" command. If it is not, the file is added to default changelist (but not submitted) with the "add" command.
To compile the Perforce code you will need the multithreaded DLL version of the P4API. The latest version can be found here: [3]. To disable the Perforce functionality comment out #define ADD_TO_PERFORCE
at the top of the Connect.inc file. This will allow you to compile VSHelper without the P4API.
Documentation for the Perforce API can be found here: [4].
Custom toolbar
The add-in creates a new custom toolbar that contains buttons for the most commands:
A tip for using custom images in add-ins: To use custom images for the new commands the add-in must provide a DLL with bitmap resources and provide two values in the registry under the HKLM\Software\Microsoft\VisualStudio\7.1\AddIns\<addin name>.Connect
key (for VS 2005 use 8.0 instead of 7.1). The values are SatelliteDLLPath
and SatelliteDLLName
. The full path to the DLL is constructed like this - <SatelliteDLLPath>\<language code>\<SatelliteDLLName>. The language code is the numeric value of the current language (1033 is English).
Then when you register the commands call:
pCommands->AddNamedCommand(m_pAddInInstance, CComBSTR("FindNext"), CComBSTR("FindNext"), CComBSTR("Find next text"), VARIANT_FALSE, // FALSE means to get the bitmap from the DLL IDB_FINDNEXT, // IDB_FINDNEXT is the bitmap's resource ID NULL, vsCommandStatusSupported+vsCommandStatusEnabled,&pFindNext);
In this case we only need bitmaps and they are supposed to be the same in all languages. So instead of providing an additional DLL for the resources we can use the original add-in DLL - VS2003Helper. To do that use these keys in your setup project:
SatelliteDLLPath=[TARGETDIR] <- this gets expanded to the installation path SatelliteDLLName='..\VS2003Helper.DLL'
When combined, the full path will be <install path>\1033\..\VS2003Helper.DLL, which when optimized points exactly to the add-in's own DLL.
Another trick to use during development: Since you will be running the DLL from the Debug or Release folder of your project, it will be hard to update the registry with the correct path. Instead, in the .rgs file use:
val SatelliteDLLPath = s '%MODULE%\..' <- this gets expanded to <project path>\Debug\VS2003Helper.DLL val SatelliteDLLName = s '..\VS2003Helper.DLL'
When combined, the full path will be <project path>\Debug\VS2003Helper.DLL\..\1033\..\VS2003Helper.DLL, which when optimized points to the DLL in the Debug folder.
Installation
- VS 2003 - Download VS2003Helper.zip and run Setup.exe.
- VS 2005 - Download VS2005Helper.zip and run Setup.exe.
-
The add-in will not automatically bind any of the commands to key combinations. You must do so manually in Visual Studio from Tools\Options\Keyboard. All commands are prefixed with VS2003Helper.Connect or VS2005Helper.Connect.
Building the code
The source code contains projects for VS 2003 and 2005. All the magic happens in the Connect.inc file, which is shared between the two projects. The bitmaps for the toolbar buttons are also shared.
If you want to use the Perforce functionality you need to download P4API from here: [3]. In the project settings replace D:\Work\Libs\p4api with actual path to the API.
Licensing
The source code, the binaries and this article are owned by Pandemic Studios. They can be freely used for commercial and non-commercial purposes under the terms of the MIT license. A copy of the license is included in the readme.rtf file in VSHelperSrc.zip.
Known problems and future development
The Output
command always shows the Output window on top. There may be a way to preserve the Z-order between the Output window and the Find Results windows. Possibly the add-in can subclass these windows and keep track of which is activated last.
The Perforce functionality uses only the default settings - depot, workspace, user, password. Instead, they can be added to the Settings dialog. Even better, the settings can probably be retrieved from solution properties. Also in VS 2003 only C++ projects are supported. A future version may support different project types - C#, VB, etc.
Links
[1] Debugger support for CRC hash values
[2] HOWTO: Getting Project and ProjectItem events from a Visual Studio .NET add-in
[3] Download latest multithreaded DLL version of the P4API
[4] Perforce API documentation
[5] HOWTO: Removing commands and UI elements during Visual Studio .NET add-in uninstallation
History
- Jan, 2006 - First version
- The add-in compiles and runs for VS 2003 and VS 2005 Beta 2. Binaries provided for VS 2003 only
- Mar, 2006 - Few fixes
- Fixed a compatibility issue with Visual Assist. The text from the
Copy
andCut
commands wasn't added to the Visual Assist's Paste Menu - Optimized the
Locate
command for large solutions
- Fixed a compatibility issue with Visual Assist. The text from the
- Oct, 2006 - Version 2.0: new features and bug fixes
- Additions to the
Opposite
command - Settings dialog to enable some of the features and to configure the
Opposite
command - Perforce helper - automatically add new files to Perforce
- Toolbar with icons for most commands
- Fixed a bug in the
Copy
andCut
commands that corrupts the clipboard when they are used in the image editor - Added uninstall script to remove the commands and the toolbar from Visual Studio. For more details on uninstall scripts see here: [5]
- Provided binaries for VS 2005
- Additions to the
- Feb, 2007 - Version 2.2: new features and bug fixes
- Added
ClearBookmarks
command for VS 2005 - Added support for the Numpad Clear key for VS 2005
- Added a list to pick which windows to close with the
Esc
command - Fixed a crash when opening some corrupted .vcproj files
- Published under the MIT license
- Added
About Ivo Beltchev
Ivo started programming in 1985 on an Apple ][ clone. He graduated from Sofia University, Bulgaria with a MSCS degree. Ivo has been working as a professional programmer for over 10 years, and as a professional game programmer for over 8. He is currently employed in Pandemic Studios, a video game company in Los Angeles, California. Click here to view Ivo Beltchev's online profile. |