59

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;
}
Matt
  • 74,352
  • 26
  • 153
  • 180
Harold Bamford
  • 1,589
  • 1
  • 14
  • 26
  • Awesome. So, do you invoke this with "devenv /command Macros.MyMacros.visual_studio_open_file myFile someLineNumber"? – EndangeredMassa Dec 12 '08 at 14:55
  • No, this is just code to open up a file with an existing instance of devenv. The idea is that if I have to have specialized code to open the file, this is that code. Sorry I didn't get back to you before, I just now noticed your comment. – Harold Bamford Dec 17 '08 at 17:00
  • 3
    I wonder if anyone has a cleaner way to do this as of VS 2010? – Dan Fitch Aug 24 '10 at 20:52

15 Answers15

31

With VS2008 SP1, you can use the following command line to open a file at a specific line in an existing instance :

devenv /edit FILE_PATH /command "edit.goto FILE_LINE"

Source

Fouré Olivier
  • 319
  • 3
  • 3
  • 7
    I tried this with no running instance and it worked--VS2008 started up, opened the file, and went to the indicated line. I then tried it on a different file (with VS2008 still running) and it opened the file but stayed at line 1. What do you suppose is different between our setups? – Harold Bamford Sep 09 '10 at 20:44
  • 2
    I have this set up in Vim, and it works one in ten times. VS2010 – dash-tom-bang Oct 01 '10 at 00:10
  • 11
    This opens files for me in VS2010, but doesn't jump to the line unless it's opening a new instance of Visual Studio. – idbrii Oct 07 '11 at 21:27
  • 2
    Still not the solution. The right solution is one post below. – Alexis Pautrot Sep 18 '12 at 13:54
  • 2
    This worked for VS2013 for me. Even with VS2013 not already open. However, I needed to use a Visual Studio command prompt or specify full path to `devenv`. I expect one can add the path to PATH environment variable as well. – thomthom Jan 23 '14 at 21:09
  • I can't get this to work - I'm on VS2010 Pro, if I have the /edit flag, the /command flag seems to be completely ignored. If I don't have the /edit flag, it opens a new instance of VS. – Lynden Shields Apr 24 '14 at 03:55
  • Update: Still works for VS2017! You will get a status bar message in the bottom left that reads `Command "edit.goto" is not available.` if the file you provide cannot be opened. – Chad Sep 15 '19 at 03:50
29

Elaborating on Harold question and answer, I adapted the C++ solution (that I first adopted) to C#. It is much simpler (that is my first C# program!). One just need to create a project, add references to "envDTE" and "envDTE80" and drop the following code:

using System;
using System.Collections.Generic;
using System.Text;

namespace openStudioFileLine
{
    class Program    
    {
        [STAThread]
        static void Main(string[] args)     
        {
            try          
            {
                String filename = args[0];
                int fileline;
                int.TryParse(args[1], out fileline);
                EnvDTE80.DTE2 dte2;
                dte2 = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");
                dte2.MainWindow.Activate();
                EnvDTE.Window w = dte2.ItemOperations.OpenFile(filename, EnvDTE.Constants.vsViewKindTextView);
                ((EnvDTE.TextSelection)dte2.ActiveDocument.Selection).GotoLine(fileline, true);
            }
            catch (Exception e)          
            {          
                Console.Write(e.Message);      
            }
        }
    }
}

One then just calls openStudioFileLine path_to_file numberOfLine.

Hope that may help !

Omer Raviv
  • 11,409
  • 5
  • 43
  • 82
reder
  • 1,098
  • 7
  • 9
  • Wonderful ! I had to set "VisualStudio.DTE.9.0" to get it working (it throws to me a COMException with message MK_E_UNAVAILABLE otherwise) – Alexis Pautrot Sep 18 '12 at 13:53
  • Maybe you will adapt your code to open file without pathname that is part of an open project (path should be deducted from project data)? And by the following input: FILENAME:LINE_NUM. This format is widely used in Unix world. Ideally, there should be an interactive command, like Ctrl+G which jumps to LINE_NUM, but it should also jump to different file if "FILENAME:" part is supplied – midenok Aug 28 '16 at 07:29
  • Great solution - I suggest adding below to use the format I get in exceptions, i.e. ends with <:line n> int iPos = filename.IndexOf(":line "); if (iPos > 0) { string sLineNumber = filename.Substring(iPos + 6); int.TryParse(sLineNumber, out fileline); filename = filename.Remove(iPos); } – Paul McCarthy Aug 07 '19 at 13:51
14

Based on reder answer I have published repository with source, here is binary(.net2.0)

I also add support for multiple VS versions

usage: <version> <file path> <line number> 

Visual Studio version                 value 
VisualStudio 2002                     2 
VisualStudio 2003                     3 
VisualStudio 2005                     5 
VisualStudio 2008                     8 
VisualStudio 2010                    10 
VisualStudio 2012                    12 
VisualStudio 2013                    13 

Example using from GrepWin:

VisualStudioFileOpenTool.exe 12 %path% %line%
Community
  • 1
  • 1
diimdeep
  • 1,096
  • 17
  • 27
  • Nice step making it work on many versions and hosted on github. Strangely, it kind of works but not in the interesting cases. It works when run from debugger, not from command line or FxCop. VS2010, Win7. – Stéphane Gourichon Sep 17 '13 at 13:49
  • 1
    Thanks a lot. In my Qt applications I have a logger that records the source file/line number of each log entry. With this code I can now add a button on the logger widget to jump to the line in Visual Studio.. – drescherjm Sep 24 '14 at 16:11
4

Pretty old thread, but it got me started so here's another example. This AutoHotkey function opens a file, and puts the cursor on a particular rowand column.

; http://msdn.microsoft.com/en-us/library/envdte.textselection.aspx
; http://msdn.microsoft.com/en-us/library/envdte.textselection.movetodisplaycolumn.aspx
VST_Goto(Filename, Row:=1, Col:=1) {
    DTE := ComObjActive("VisualStudio.DTE.12.0")
    DTE.ExecuteCommand("File.OpenFile", Filename)
    DTE.ActiveDocument.Selection.MoveToDisplayColumn(Row, Col)
}

Call with:

VST_Goto("C:\Palabra\.NET\Addin\EscDoc\EscDoc.cs", 328, 40)

You could translate it pretty much line by line to VBScript or JScript.

Wade Hatler
  • 1,785
  • 20
  • 18
3

Here is Python variation of Harold's solution:

import sys
import win32com.client

filename = sys.argv[1]
line = int(sys.argv[2])
column = int(sys.argv[3])

dte = win32com.client.GetActiveObject("VisualStudio.DTE")

dte.MainWindow.Activate
dte.ItemOperations.OpenFile(filename)
dte.ActiveDocument.Selection.MoveToLineAndOffset(line, column+1)

It shows how to go to specified line + column.

Evgeny Panasyuk
  • 9,076
  • 1
  • 33
  • 54
3

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.

Evgeny Panasyuk
  • 9,076
  • 1
  • 33
  • 54
  • I try to open a file and find a keyword. `dte.ExecuteCommand "Edit.Find", "ID=""label1"""` doesn't work as VS says "The following specified text as not found: ID=label1". It seems the quotes get swallowed. Do you know why? – Gqqnbig Sep 05 '17 at 17:06
  • @LoveRight I am not sure what are you referring to. Script on link does not have any "ExecuteCommand". – Evgeny Panasyuk Sep 10 '17 at 13:56
3

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?

2

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());
}
Mungo64
  • 41
  • 4
  • "DTE dte = (DTE) Marshal.GetActiveObject("VisualStudio.DTE");" (i.e without the number) worked for me - thanks – Declan Taylor Jan 03 '22 at 21:07
  • Had to use another version of Marshall class though, see: https://stackoverflow.com/questions/58010510/no-definition-found-for-getactiveobject-from-system-runtime-interopservices-mars – Declan Taylor Jan 03 '22 at 21:09
1

For reference here is the ENVDE written in C# (using O2 Platform inside VisualStudio to get a reference to the live DTE object)

var visualStudio = new API_VisualStudio_2010();

var vsDTE = visualStudio.VsAddIn.VS_Dte;
//var document = (Document)vsDTE.ActiveDocument;
//var window =  (Window)document.Windows.first();           
var textSelection  = (TextSelection)vsDTE.ActiveDocument.Selection;
var selectedLine = 1;
20.loop(100,()=>{
                    textSelection.GotoLine(selectedLine++);
                    textSelection.SelectLine();
                });
return textSelection;

This code does a little animation where 20 lines are selected (with a 100ms interval)

Dinis Cruz
  • 4,161
  • 2
  • 31
  • 49
1

The correct wingrep command line syntax to force a new instance and jump to a line number is:

"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" $F /command "edit.goto $L"

Replace the studio version number with the correct version for your setup.

Pbk1303
  • 3,702
  • 2
  • 32
  • 47
1

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());
}
Joe
  • 625
  • 5
  • 3
0

I was about to ask this question because when you get the "yellow screen of death" when debugging a web application, you want to quickly go to the file and line that it gives you in the stacktrace e.g:

[ContractException: Precondition failed: session != null]
   System.Diagnostics.Contracts.__ContractsRuntime.TriggerFailure(ContractFailureKind kind, String msg, String userMessage, String conditionTxt, Exception inner) in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Controllers\CustomErrorsPageController.cs:0
   System.Diagnostics.Contracts.__ContractsRuntime.ReportFailure(ContractFailureKind kind, String msg, String conditionTxt, Exception inner) in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Controllers\CustomErrorsPageController.cs:0
   System.Diagnostics.Contracts.__ContractsRuntime.Requires(Boolean condition, String msg, String conditionTxt) in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Controllers\CustomErrorsPageController.cs:0
   IAS_UI.Web.IAS_Session..ctor(HttpSessionStateBase session) in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Web\IAS_Session.cs:15
   IAS_UI.Controllers.ServiceUserController..ctor() in C:\_svn\IntegratedAdaptationsSystem\Source\IntegratedAdaptationsSystem\IAS_UI\Controllers\ServiceUserController.cs:41

Say I want to go to ServiceUserController.cs at line 41. Usually I would open Visual Studio and do it manually but then I wrote a little Autohotkey script which does it.

To open it, you will highlight the filename and line number e.g. ServiceUserController.cs:41 and thereafter press your shortcut Alt + v. Here is the code for it:

$!v::
if (NOT ProcessExists("devenv.exe"))
{
    MsgBox, % "Visual Studio is not loaded"
}
else
{
    IfWinExist, Microsoft Visual Studio
    {
        ToolTip, Opening Visual Studio...
        c := GetClip()

        if (NOT c) {
            MsgBox, % "No text selected"
        }
        else 
        {
            WinActivate ; now activate visual studio
            Sleep, 50
            ; for now assume that there is only one instance of visual studio - handling of multiple instances comes in later

            arr := StringSplitF(c, ":")

            if (arr.MaxIndex() <> 2) {
                MsgBox, % "Text: '" . c . "' is invalid."
            }
            else {
                fileName := arr[1]
                lineNumber := arr[2]

                ; give focus to the "Find" box
                SendInput, ^d 

                ; delete the contents of the "Find" box
                SendInput, {Home}
                SendInput, +{End}
                SendInput, {Delete}

                ; input *** >of FILENAME *** into the "Find" box
                SendInput, >of{Space}
                SendInput, % fileName

                ; select the first entry in the drop down list
                SendInput, {Down}
                SendInput, {Enter}

                ; lineNumber := 12 remove later

                ; open the go to line dialog
                SendInput, ^g
                Sleep, 20

                ; send the file number and press enter
                SendInput, % lineNumber
                SendInput {Enter}
            }
        }    
        ToolTip
    }
}
return

You will want to paste the following "utility functions" before it:

GetClip()
{
    ClipSaved := ClipboardAll
    Clipboard=
    Sleep, 30
    Send ^c
    ClipWait, 2
    Sleep, 30
    Gc := Clipboard
    Clipboard := ClipSaved
    ClipSaved=

    return Gc
}

ProcessExists(procName)
{
    Process, Exist, %procName%

    return (ErrorLevel != 0)
}

StringSplitF(str, delimeters)
{
    Arr := Object()

    Loop, parse, str, %delimeters%,
    {
        Arr.Insert(A_LoopField)
    }

    return Arr
}
Tahir Hassan
  • 5,715
  • 6
  • 45
  • 65
0

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.

EndangeredMassa
  • 17,208
  • 8
  • 55
  • 79
0

Using this command works for me, as long as Visual Studio is NOT open already. "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" /edit "ABSOLUTEFILEPATH_FILENAME.CPP" /command "Edit.GoTo 164"

If it is already open, then sometimes it works and goes to the right line, but then it just stops working and I have never figured out why. Looks like Microsoft is aware of the issue but have said they "Will Not Fix" it, unless more people complain. So if it's still an issue I'd suggest commenting here: https://connect.microsoft.com/VisualStudio/Feedback/Details/1128717

Craigw1701
  • 19
  • 1
0

Slightly simplified version of the answer from OnceUponATimeInTheWest:

using System.Runtime.InteropServices;

private static void OpenFileAtLine(string file, int line) {
    dynamic vs = Marshal.GetActiveObject("VisualStudio.DTE");
    dynamic window = vs.ItemOperations.OpenFile(path);
    window.Selection.GotoLine(line, true);
}

It uses dynamics instead of Reflection to make the code a bit shorter and more readable.