Open a file in Visual Studio at a specific line number

Viewed 23779

I have a utility (grep) that gives me a list of filenames and a line numbers. After I have determined that devenv is the correct program to open a file, I would like to ensure that it is opened at the indicated line number. In emacs, this would be:

emacs +140 filename.c

I have found nothing like this for Visual Studio (devenv). The closest I have found is:

devenv /Command "Edit.Goto 140" filename.c

However, this makes a separate instance of devenv for each such file. I would rather have something that uses an existing instance.

These variations re-use an existing devenv, but don't go to the indicated line:

devenv /Command "Edit.Goto 140" /Edit filename.c
devenv /Command  /Edit filename.c "Edit.Goto 140"

I thought that using multiple "/Command" arguments might do it, but I probably don't have the right one because I either get errors or no response at all (other than opening an empty devenv).

I could write a special macro for devenv, but I would like this utility to be used by others that don't have that macro. And I'm not clear on how to invoke that macro with the "/Command" option.

Any ideas?


Well, it doesn't appear that there is a way to do this as I wanted. Since it looks like I'll need to have dedicated code to start up Visual Studio, I've decided to use EnvDTE as shown below. Hopefully this will help somebody else.

#include "stdafx.h"

//-----------------------------------------------------------------------
// This code is blatently stolen from http://benbuck.com/archives/13
//
// This is from the blog of somebody called "BenBuck" for which there
// seems to be no information.
//-----------------------------------------------------------------------

// import EnvDTE
#pragma warning(disable : 4278)
#pragma warning(disable : 4146)
#import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("8.0") lcid("0") raw_interfaces_only named_guids
#pragma warning(default : 4146)
#pragma warning(default : 4278)

bool visual_studio_open_file(char const *filename, unsigned int line)
{
    HRESULT result;
    CLSID clsid;
    result = ::CLSIDFromProgID(L"VisualStudio.DTE", &clsid);
    if (FAILED(result))
        return false;

    CComPtr<IUnknown> punk;
    result = ::GetActiveObject(clsid, NULL, &punk);
    if (FAILED(result))
        return false;

    CComPtr<EnvDTE::_DTE> DTE;
    DTE = punk;

    CComPtr<EnvDTE::ItemOperations> item_ops;
    result = DTE->get_ItemOperations(&item_ops);
    if (FAILED(result))
        return false;

    CComBSTR bstrFileName(filename);
    CComBSTR bstrKind(EnvDTE::vsViewKindTextView);
    CComPtr<EnvDTE::Window> window;
    result = item_ops->OpenFile(bstrFileName, bstrKind, &window);
    if (FAILED(result))
        return false;

    CComPtr<EnvDTE::Document> doc;
    result = DTE->get_ActiveDocument(&doc);
    if (FAILED(result))
        return false;

    CComPtr<IDispatch> selection_dispatch;
    result = doc->get_Selection(&selection_dispatch);
    if (FAILED(result))
        return false;

    CComPtr<EnvDTE::TextSelection> selection;
    result = selection_dispatch->QueryInterface(&selection);
    if (FAILED(result))
        return false;

    result = selection->GotoLine(line, TRUE);
    if (FAILED(result))
        return false;

    return true;
}
14 Answers

Here is VBS variation of Harold's solution: link to .vbs script.

open-in-msvs.vbs full-path-to-file line column

Windows supports VBScript natively - no need for compilation or any additional interpreters.

These C# dependencies on project references are completely unecessary. Indeed much of the code here is overly verbose. All you need is this.

using System.Reflection;
using System.Runtime.InteropServices;

private static void OpenFileAtLine(string file, int line) {
    object vs = Marshal.GetActiveObject("VisualStudio.DTE");
    object ops = vs.GetType().InvokeMember("ItemOperations", BindingFlags.GetProperty, null, vs, null);
    object window = ops.GetType().InvokeMember("OpenFile", BindingFlags.InvokeMethod, null, ops, new object[] { file });
    object selection = window.GetType().InvokeMember("Selection", BindingFlags.GetProperty, null, window, null);
    selection.GetType().InvokeMember("GotoLine", BindingFlags.InvokeMethod, null, selection, new object[] { line, true });
}

Simples eh?

This is my working C# solution for Visual Studio 2017 (15.9.7)

For other versions of VS just change the version number (i.e. "VisualStudio.DTE.14.0")

todo: Add Reference->Search 'envdte'->Check Checkbox for envdte->Click OK

using EnvDTE;        

private static void OpenFileAtLine(string file, int line)
{
    DTE dte = (DTE)  Marshal.GetActiveObject("VisualStudio.DTE.15.0");
    dte.MainWindow.Visible = true;
    dte.ExecuteCommand("File.OpenFile", file);
    dte.ExecuteCommand("Edit.GoTo", line.ToString());
}

The version posted by @Mungo64 worked for me, but of course the version number is always changing, so I made a version that automatically searches until we find it.

Add Reference->Search 'envdte'->Check Checkbox for envdte->Click OK

//using EnvDTE; //I didn't use the using directive as it causes ambiguity in another module I'm using.

private static void OpenFileAtLine(string file, int line)
{
    //The number needs to be rolled to the next version each time a new version of visual studio is used... 
    EnvDTE.DTE dte = null;


    for (int i = 25; i > 8; i--) {
        try
        {
            dte = (EnvDTE.DTE)Marshal.GetActiveObject("VisualStudio.DTE." + i.ToString() + ".0");
        }
        catch (Exception ex)
        {
            //don't care... just keep bashing head against wall until success
        }
    }

    //the following line works fine for visual studio 2019:
    //EnvDTE.DTE dte = (EnvDTE.DTE)Marshal.GetActiveObject("VisualStudio.DTE.16.0");
    dte.MainWindow.Visible = true;
    dte.ExecuteCommand("File.OpenFile", file);
    dte.ExecuteCommand("Edit.GoTo", line.ToString());
}

I can't figure out a way to do this with straight command-line options. It looks like you will have to write a macro for it. Supposedly, you can invoke them like so.

devenv /command "Macros.MyMacros.Module1.OpenFavoriteFiles"

So, you can probably create a macro that takes a filename and a line number, then opens the file and jumps to the proper place. But, I don't know that you can specify a same-instance flag somewhere, or not.

Related