Vim Tips Wiki
Advertisement

Duplicate tip

This tip is very similar to the following:

These tips need to be merged – see the merge guidelines.

Previous TipNext Tip

Tip: #580 - Using DDE to open the current Vim file in Visual Studio

Created: October 6, 2003 10:43 Complexity: intermediate Author: Francois Leblanc Version: 6.0 Karma: 171/53 Imported from: Tip#580

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/

Comments

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(--AT--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; 
'

mosh--AT--cs.albany.edu , October 6, 2003 18:46


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

dunderxiii--AT--hotmail.com , October 10, 2003 7:39


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.

Anonymous , October 22, 2003 13:47


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

orthober--AT--msoe.edu , January 29, 2004 8:30


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)

rdparker--AT--butlermfg.com , March 24, 2004 9:16


An alternate distribution of Win32::GuiTest can be found at https://sourceforge.net/projects/winguitest/

Note: WaitWindow was a recent addition.


Anonymous , April 23, 2004 5:00


To get the current column as well as the current line: --remote-silent +"call cursor($(CurLine),$(CurCol))" +"normal zz" $(ItemPath)

faceprint--AT--faceprint.com , September 1, 2004 10:01


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


Da5id , April 9, 2005 14:59


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; 
} 


litewerk--AT--gmail.com , October 6, 2005 23:40


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 ); 
}

litewerk--AT--gmail.com , October 6, 2005 23:45


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.

litewerk--AT--gmail.com , October 10, 2005 18:10


Advertisement