sponsor Vim development Vim logo Vim Book Ad

intermediate Tip #580: Switching back and forth between ViM and Visual Studio .NET

 tip karma   Rating 69/24, Viewed by 7387 

created:   October 6, 2003 10:43      complexity:   intermediate
author:   Francois Leblanc      as of Vim:   6.0

This tip is for when you work on a devstudio project and need the debugger heavily and/or can't stay in ViM all the time. But when it comes time to make changes you want to do them in ViM and don't want to relocate the file and line number.

After you have made the change and perhaps opened another file or navigated your way to a new section of the code you want to switch back to devstudio at the spot you were in ViM. It may be because you want to set a breakpoint or any reason.

The easy part:
Launching ViM from DevStudio.NET is easy.

From the DevStudio menu item Tools|External Tools...  add a new entry where:
The "Command Line" field is set to the path of the ViM executable
The "Arguments" field contains: --servername gmain --remote-silent +$(CurLine) +"normal zz" $(ItemPath)
The "Initial Directory" may optionally contain: $(ItemDir)

This will start a ViM session or connect to an already existing one (--remote-silent) named gmain (--servername gmain). This will use only one instance of ViM for all devstudio editing. It will open the file specified by $(ItemPath) and set the cursor pos to $(CurLine). It will also execute the normal command zz to center the cursor.

You can then create a keyboard shorcut to map to this tool (Tools|Options||Environment|Keyboard, select Tools.ExternalCommandX) and you will be able to switch to ViM quickly.


The hard part:
Opening a file in an existing DevStudio.NET instance is a pain and setting the cursor to a line number is even more so.

DevStudio cannot be controlled by the command line. To open a file in an existing instance a DDE call must be initiated. Its an old and obsolete technology called Dynamic Data Exchange used for interprocess communication. When you click on a .cpp file in the Windows Explorer it calls devenv.exe with the /DDE switch (its undocumented) and sends it an Open DDE command. You can see it for yourself if you look at the file type mapping of .cpp in the Windows Explorer (if you haven't already changed them to open ViM :-)). The Explorer shell is DDE enabled but I found no way to send DDE from the command line (I didn't really look for it either ;-)). So I wrote a small C++ console app from the code I got from an Experts Exchange question. I formatted the code, renamed references from DevStudio to DevEnv and put it in a project.

Setting the line number is a different problem. I wrote a Perl script using the Win32::GuiTest module. This module allows interacting with the Windows GUI and provides a very useful function called SendKeys. The script finds the Visual C++ window (if you are using a different language change the script) and sends it: a CTRL-G, the current line number as specified on the command line and ENTER.

It is integrated in ViM by a function (in _vimrc) that gets the current file name and line number and silently executes the script:
function! DevEnvDDE()
let cmd = '!devenvdden.pl %:p ' . line(".")
silent execute cmd
endfunction

All that is left is to map the function to a key.


You can get the source files for the Perl script and DDE project at http://dunderxiii.tripod.com/vimtips/devenvdde.zip
The original DDE code was taken at http://www.experts-exchange.com/Programming/Programming_Platforms/Win_Prog/Q_20489782.html
Win32::GUITest is located at http://groups.yahoo.com/group/perlguitest/

 rate this tip  Life Changing Helpful Unfulfilling 

<<Cut&Paste; without too much newlines, eg. into WORD | Using vim to view source and edit textarea in mozilla/firebird >>

Additional Notes

[email protected], October 6, 2003 18:46
I have the script send-to-msdev.sh, that uses Perl + Win32::Ole
to open files in VC. You can even set breakpoints from perl,
this script has pointers to MSDN documentation.

  http://www.cs.albany.edu/~mosh/Perl/send-to-msdev.ksh

Caveats: All win32 perl to be buggy in some respect,
but active perl55 is the only one that can talk to vc.
Very few commands in msdev can be automated, some dont
even have a name, you just have to click  to get to them!

- Mohsin

"Her bed is India; there she lies, a pearl" - Troillus and Cressida 1.1, WS.

# Guts of the OLE messages that work (part of sh script)

d:/perl/perl_55x/bin/perl.exe -e '
push(@INC,"d:/perl/perl_55x/lib/site");
require Win32::OLE;
$app = Win32::OLE->GetActiveObject("MSDev.Application");
$app = Win32::OLE->new("MSDEV.APPLICATION") if ! defined $app;
die "Cant open MsDev.\n" unless $app;
$app->{"WindowState"} = 1 if $app->{"WindowState"} == 2;
$app->{"Visible"}=1;
$app->{"Active"}=1;

$file = $app->{"Documents"}->Open("'$VIEWFILE'");
die "Cant open file='$VIEWFILE' in msdev.\n" unless $file;
print( $app->FullName()," ",$file->FullName()," type=",$file->Type(),".\n" );
print("PWD=",$app->CurrentDirectory(),".\n");

$file->Selection()->GotoLine("'$GOTOLINE'") if $ENV{"GOTOLINE"};
# dsMatchWord is in vc/include/objmodel/textdefs.h
$file->Selection()->FindText($ENV{"FINDTEXT"},2) if $ENV{"FINDTEXT"};
$file->Selection()->Selectline(); # highlight it.

exit 0;
'
[email protected], October 10, 2003 7:39
I udated the DevEnvDDE program to connect to Visual Studio .NET 2003. The code now supports an extra command line parameter that specifies the visual studio instance to open the file in (VS6, VSNET or VSNET2003). Simply download the new sources (same link).

Note: it now defaults to Visual Studio .NET 2003 instead of Visual Studio .NET
Anonymous, October 22, 2003 13:47
Thanks for this!

Just a small note regarding the perl script - you don't say which version of Win32::GUITest you are using but I found that I had to change WaitWindow to FindWindowLike to get the script working.  WaitWindow must be deprecated or something.
[email protected], January 29, 2004 8:30
Just to let you know, to open a file with Visual Studio (At least the current versions) you simple can put the file name as an arument to the command line.  

Example:

"C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\devenv.exe" "BE-NotInDirectory\main.cpp" "CC-late\main.c
pp" "DO\Lab 5.cpp" "MG\Lab5.cpp" "NathanK\main.cpp" "NH\func.h" "NH\func.cpp" "NH\main.cpp" "NM-late\main.cpp"

Would open all of these files.

What I am looking for is a way to tell VS to Auto Print these files.  I have also made a program that will SENDKEYS to VS to print these, but I would like a better way.

Thanks
Jeff O
[email protected], March 24, 2004 9:16
For Visual Studio 6.0 replace ItemPath and ItemDir with FilePath and FileDir, as follows:

Arguments:  --servername gmain --remote-silent +$(CurLine) +"normal zz" "$(FilePath)"
(Optional) Initial directory: $(FileDir)
Anonymous, April 23, 2004 5:00
An alternate distribution of Win32::GuiTest can be found at https://sourceforge.net/projects/winguitest/

Note: WaitWindow was a recent addition.


[email protected], September 1, 2004 10:01
To get the current column as well as the current line:
--remote-silent +"call cursor($(CurLine),$(CurCol))" +"normal zz" $(ItemPath)
Da5id, April 9, 2005 14:59
::Nice::  This is a great hack. I had to make the same change to
devenvdden.pl described by a previous poster, namely: change the two
occurrences of 'WaitForWindow' to 'FindWindowLike'.  (It threw me at
first that my Mozilla Firefox window kept coming up; I was led to this
post by googling for 'vim "visual studio .net"', which happens to
match--d'oh!)

I also got a little confused when trying to exercise the individual
pieces one at a time to make sure I understood how each worked. Turns
out you have to use *absolute* paths. But once I got it working--man,
what a nice little hack.  Thanks! :-D
[email protected], October 6, 2005 23:40
Here's some code to add to DevEnvDDE.cpp to make it do the GoToLine thing from C++, so you can just run this thing from the command line and not have to do the scripting afterword.  You might have to add some headers to stdafx (atlstr.h, afxwin.h) and add MFC and possible ATL support to the project...

void PeekAndYield()
{
MSG Msg;
while( PeekMessage( &Msg;, NULL, 0, 0, PM_REMOVE ) ) {
TranslateMessage(&Msg;);
DispatchMessage(&Msg;);
}
}

class Find_window {
public:
Find_window( std::string find_str_ ) : find_str( find_str_ ), hwnd( NULL ) {}
std::string find_str;
HWND hwnd;
};

BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam )
{
Find_window* fw = (Find_window*) lParam;
CWnd cwnd;
cwnd.Attach( hwnd );
CString cstr;
cwnd.GetWindowText( cstr );
cwnd.Detach();
std::string str( cstr );
typedef std::string::iterator str_iterator;
std::string find_str = "Microsoft Visual C++";
std::pair<str_iterator,str_iterator> nxt = nbs::find_section( str.begin(), str.end(), find_str.begin(), find_str.end() );
if( nxt.first == str.end() ) {
return TRUE;
} else {
fw->hwnd = hwnd;
return FALSE;
}
}


void MSDEV_OpenFile(char* pszFileName, int line_number, EVisualStudioType vsType )
{
//...
//Lots of code, leave it the same...
//...
if( line_number < 0 ) line_number = -line_number;
if( line_number > 0 ) {
Find_window fnd( "Microsoft Visual C++" );
EnumWindows( EnumWindowsProc, (LPARAM)&fnd; );
if( fnd.hwnd ) {
::SetForegroundWindow( fnd.hwnd );
PeekAndYield();
keybd_event( VK_CONTROL, 0, 0, 0 );
keybd_event( 'G', 0, 0, 0 );
PeekAndYield();
keybd_event( 'G', 0, KEYEVENTF_KEYUP, 0 );
keybd_event( VK_CONTROL, 0, KEYEVENTF_KEYUP, 0 );
PeekAndYield();
std::string lstr = nbs::to_str( line_number );
for( size_t i = 0; i < lstr.size(); ++i ) {
keybd_event( lstr[i], 0, 0, 0 );
keybd_event( lstr[i], 0, KEYEVENTF_KEYUP, 0 );
}
PeekAndYield();
keybd_event( VK_RETURN, 0, 0, 0 );
keybd_event( VK_RETURN, 0, KEYEVENTF_KEYUP, 0 );
PeekAndYield();
}
}
}


int _tmain(int argc, _TCHAR* argv[])
{
if( argc <= 1 ){
usage();
return 1;
}
int line_number = 0;

if( argc > 2 ){
line_number = atoi( argv[2] );
}

EVisualStudioType vsType = eVSNET2003;
if( argc > 3 ){
if(stricmp(argv[3], "VS6") == 0){
vsType = eVS6;
}else if(stricmp(argv[3], "VSNET") == 0){
vsType = eVSNET;
}else if(stricmp(argv[3], "VSNET2003") == 0){
vsType = eVSNET2003;
}else{
usage();
return 1;
}
}

MSDEV_OpenFile(argv[1], line_number, vsType);
return 0;
}

[email protected], October 6, 2005 23:45
I forgot the code for find_section...


template <class T1,class T2>
std::pair<T1,T1> find_section( T1 first, T1 last, T2 first2, T2 last2 )
{
   std::pair<T1,T1> result( last, last );

   if (first2 != last2)
   {
      first = std::find( first, last, *first2 );
      while (first != last)
      {
         T1 temp = first;
         ++temp;
         T2 temp2 = first2;
         ++temp2;
         while ( (temp != last) && (temp2 != last2) && (*temp == *temp2) )
         {
            temp++;
            temp2++;
         }
         if (temp2 == last2)
         {
            result.first = first;
            result.second = temp;
            break;
         }    
         ++first;
       first = std::find( first, last, *first2 );
      }
   }

   return result;
}

and to_str...

std::string to_str( int i )
{
char buffer[34];
return itoa( i, buffer, 10 );
}
[email protected], October 10, 2005 18:10
I am dumb.  No need for that find_section junk.  Just do this...

BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam )
{
Find_window* fw = (Find_window*) lParam;
CWnd cwnd;
cwnd.Attach( hwnd );
CString cstr;
cwnd.GetWindowText( cstr );
cwnd.Detach();
std::string str( cstr );
if( str.find( "Microsoft Visual C++" ) != std::string::npos ) {
fw->hwnd = hwnd;
return FALSE;
} else {
return TRUE;
}
}

If anybody ever actually reads this, email me or something so I know I wasn't posting all this junk in vain.  Also, the reason I wanted it all in one exe was so I could use it with WinDiff, which doesn't let you run scripts.
If you have questions or remarks about this site, visit the vimonline development pages. Please use this site responsibly.
Questions about Vim should go to [email protected] after searching the archive. Help Bram help Uganda.
    stats
Sponsored by Web Concept Group Inc. SourceForge.net Logo