locked
Addin for js file failed after upgrade to Visual Studio 2012 RRS feed

  • Question

  •  I have an addin that adds context menus to both cs file and js file. It works fine in VS 2010 that the right set of addin show depending on it is a cs file or a js file. After upgrade to VS 2012, when I right click on the text editor for js file, it shows the addin menu meant for the C# but not the one for js file. It seems VS 2012 confused with which context menu to show. If I removed context menu for C# in my addin, it still does not show the context menu added for javascript file (under "Script Context")?

            public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
            {
                _applicationObject = (DTE2)application;
                _addInInstance = (AddIn)addInInst;

                _solutionEvents = _applicationObject.Events.SolutionEvents;
                _solutionEvents.BeforeClosing += new _dispSolutionEvents_BeforeClosingEventHandler(solutionEvents_BeforeClosing);
                _solutionEvents.Opened += new _dispSolutionEvents_OpenedEventHandler(solutionEvents_Opened);

                _commands = (Commands2)_applicationObject.Commands;
                object[] contextGUIDS = new object[] { };
                CommandBar commandBar2 = null;
                //JavaScript commands
                try
                {
                    //Add the command
                    _rightCommand = _commands.AddNamedCommand2(_addInInstance, "JavaScriptString", RightClickToolNameJS, RightClickToolTip, true, 244, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
                    _rightCommand.Bindings = new object[] { "Text Editor::ctrl+r,ctrl+j" };
                    commandBar2 = ((CommandBars)_applicationObject.CommandBars)["Script Context"];
                    _rightCommand.AddControl(commandBar2, 1);
                }
                catch (ArgumentException)
                {
                    //If we are here, then the exception is probably because a command with that name
                    //  already exists. If so there is no need to recreate the command and we can
                    //  safely ignore the exception.
                }

                //CSharp commands
                CommandBar refactorContextMenu = null;
                CommandBar refactorMenu = null;
                try
                {
                    //Add the command
                    _rightCommandCS = _commands.AddNamedCommand2(_addInInstance, "CSharpStrings", RightClickToolNameCS, RightClickToolTip, true, 244, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
                    _rightCommandCS.Bindings = new object[] { "Text Editor::ctrl+r,ctrl+s" };
                    _commandBar = ((CommandBars)_applicationObject.CommandBars)["MenuBar"];
                    refactorContextMenu = ((CommandBars)(_applicationObject.CommandBars))["Refactor"];
                    _rightCommandCS.AddControl(refactorContextMenu, refactorContextMenu.Controls.Count + 1);
                    refactorMenu = ((CommandBarPopup)(_commandBar.Controls["Refactor"])).CommandBar;
                    _rightCommandCS.AddControl(refactorMenu, refactorMenu.Controls.Count + 1);
                }
                catch (Exception)
                {
                    //Command already there, nothing to do
                }
            }

    Thursday, October 25, 2012 3:55 PM

Answers

  • >Reading the Remarks in http://msdn.microsoft.com/en-us/library/envdte80.commands2.addnamedcommand2.aspx. It seems parametersContextUIGUIDs and vsCommandStatusValue are used WHILE the addin is NOT loaded. So I suspect (not so clear in the documentation), once loaded it relies on QueryStatus().

    Yeah, looking over the code that handles the AddIn QueryStatus it does appear that regardless of what you return it will return S_OK. The code that is calling this code will continue command routing if the return is anything other than S_OK, but since this code seems to hard-code the result to be S_OK that can never happen :(  This seems like a bug, I will file one but I can't see any work-around since there is no exit path from this function that returns anything other than S_OK, unless it encounters an SEH exception, and you can't easily cause that from managed code, nor do you want to :)

    I suspect your QueryStatus will need to emulate the context check that the shell would normally do here. So something like:

    QueryService for SVsShellMonitorSelection and cast it to IVsMonitorSelection. Call GetCmdUIContextCookie once for each GUID for the JS and C# language services I gave above. You only have to do this once, the cookie won't change during a session of VS. Then, in your QueryStatus handler you need to check if that cookie is active by calling IVsMonitorSelection.IsCmdUIContextActive. If that returns true (i.e. pfActive != 0) then you enable the command, otherwise you disable and hide it.

    The comments on status and 'style' flags seems wrong as well as the association of vsCommandDisabledFlags with the ContextUIGUIDs parameter, I will file a doc bug to get this fixed.

    The vsCommandDisabledFlags are the values you pass in to the vsCommandStatusValue parameter and determine the default state of your commands if no handler can be found.

    If no handler is found and you supplied context GUIDs and none of them are active your command will be hidden. If you didn't supply context GUIDs then the default visiblity will be either visible (default) or hidden if you passed in vsCommandDisabledFlagsHidden (0x20) as part of your vsCommandStatusValue value. Regardless of if you supplied context GUIDs or not your default enabled state will be enabled (default) or disabled if you supplied vsCommandDisabledFlagsGrey with your vsCommandStatusValue (these names are horrible, one of the reasons I dislike DTE so much).

    CommandStyle flags have to do with how your command is displayed in the UI. There aren't many options here and if you are being displayed on a context menu there are even fewer. The default here is complicated as it depends on where the buttons are being shown, I remember having to dig into how this flag is interpreted in 2010 and it was...not at all straightforward.  From memory I *think* it breaks down like this:

    If the button appears on a toolbar it, by default, shows only its image. This is equivalent to
    vsCommandStylePict.

    If the button appears on a menu it, by default, shows its image and its text. This is equivalent to vsCommandStylePictAndText.

    I *think* you can prevent the image from being shown on a menu by explicitly specifying vsCommandStyleText, though I am not sure why you would want to do that.

    You can make both the image and the text appear on a toolbar button by supplying vsCommandStylePictAndText.

    You current values are likely wrong, you are passing:

    vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled

    However this parameter isn't expecting flags from vsCommandStatus, it is expecting values from vsCommandDisabledFlags. The value you are passing above is 1 + 2 (supported = 1, enabled = 2). However, in the vsCommandDisabledFlags enum that maps to vsCommandDisabledFlagsGrey | vsCommandDisabledFlagsHidden, which means you command would have default values of disabled and hidden.

    Your second use of these same values is also incorrect. It is expecting values from the vsCommandStyle enum, where 3 == vsCommandStylePictAndText. That is fine, but the button placed on a toolbar would end up showing both the image and the text. You probably want something like vsCommandStylePict (the menu placement will show the text regardless).


    Thursday, November 8, 2012 7:59 PM

All replies

  • I should add that, I can still use the assigned shortcut key to do the correct command in js file. But it seems it cannot bring up the correct context menu added for javascript.
    Thursday, October 25, 2012 3:57 PM
  • >It seems VS 2012 confused with which context menu to show

    Nope, you are assuming it is showing the context menu "Script Context", in 2012 it is using the 'usual' editor code window context menu, it is called "Code Window". It is surprising to me that it was showing anything else previously, but it is definitely showing the "Code Window" context menu in 2012.

    Ryan


    Friday, October 26, 2012 1:43 AM
  • I see now in 2012 both .cs and .js files (and a few other types) showing "Code Window" AND "Text Editor" menus.

    While in 2010, js only shows "Script Context" menu (not "Text Editor", nor "Code Window"), while .cs shows "Code Window" AND "Text Editor".

    1. I need to support this addin for both 2010 and 2012. How to detect which version of VS it is?
    2. I need to show the right set of my addin menus based on whether it is  cs or js file. What's the right  way to do that in both 2010 and 2012?
    3. What exactly context menus show for each different types of files in 2012 and 2010? Is there a reference that I can find out these information?

    Friday, October 26, 2012 2:57 PM
  • I don't understand what you mean by showing both, the shell only shows one context menu at a time. In theory the editor COULD show different context menus based on where you click, but in reality it doesn't.

    >I need to support this addin for both 2010 and 2012. How to detect which version of VS it is?

    DTE.Version

    >I need to show the right set of my addin menus based on whether it is  cs or js file. What's the right  way to do that in both 2010 and 2012?

    Likely by adding the proper context GUIDs to your call to AddNamedCommand so the commands will have their visibility auto-determined by the activation of said context GUIDs. For the C# editor its language service's context guid is {694DD9B6-B865-4C5B-AD85-86356E9C88DC}, the JS language service context GUID is {71d61d27-9011-4b17-9469-d20f798fb5c0}. If you put those in the context guids param to your call to AddNamed command and DON'T handle the IDTCommandTarget::QueryStatus for those commands they will be visible only when those GUIDs are active, which should only be when an editor of the given type is focused.

    >What exactly context menus show for each different types of files in 2012 and 2010? Is there a reference that I can find out these information?

    No such lists exists to my knowledge. The editor is extensible and non-standard editors can be used with arbitrary file types in Visual Studio, thus there is no list of what every editor may or may not do. You can do something like using EnableVSIPLogging to get info about context menus as you right click in the shell.

    Ryan

    Saturday, October 27, 2012 6:43 PM
  • We have an addin adding one context menu to be used for cs file (through "Refactor") and one context menu for js file (through "Script Context"), and it works fine in VS 2010 (sorry, it was my typo in my previous respond that referring to menu "Text Editor")

    In VS 2012 using the information you provided, I modified the Addin to use "Code Window" for the context menu for js, while also keep the other different one  we wanted for cs file (added to "Refactor"). The result is I found that using "Code Window" (instead of "Script Context" works in VS 2010) to add the menu for js file. The result, is both menus (one added to "Code Window" and one added to "Refactor") show up from either cs file and js file (please refer to the original code posed in the question).  I also found both menus also shows up in web.config editor, and xml file editor which we definitely don't want.

    Using EanbleVSIPLogging that you referring to, I found in VS 2010, right click on C# editor gave me:

        Guid = {D309F791-903F-11D0-9EFC-00A0C911004F}
        GuidID = 8
        CmdID = 1037
        Type = 0x00000400
        Flags = 0x00000000
        NameLoc = Code Window

    and on js editor:

        Guid = {D7E8C5E1-BDB8-11D0-9C88-0000F8040A53}
        GuidID = 103
        CmdID = 52
        Type = 0x00000400
        Flags = 0x00000000
        NameLoc = Script Context

    But now in 2012, it show the same GUID:CmdID for .cs, .js, .config, .xml:

        Guid = {D309F791-903F-11D0-9EFC-00A0C911004F}

        GuidID = 4
        CmdID = 1037
        Type = 0x00000400
        Flags = 0x00000000
        NameLoc = Code Window

    Does that mean I should pass in the same GUIID:CmdID pair I found from EnableVSIPLoggin? I expect that won't work for us. We want add a menu just available for js files, while another for just available cs files.

    Perhaps the way we add the menus isn't appropriate (see original code posted in the question) although it seems working for 2010. What's the right way to add context menu, one just to be used for js file, and one for just cs file in VS 2012?

    Tuesday, November 6, 2012 6:21 PM
  • >The result is I found that using "Code Window" (instead of "Script Context" works in VS 2010) to add the menu for js file.

    This is confusing, if using "Code Window" works in 2010 it implies the JS editor is showing the standard editor context menu in 2010. If so it is unclear what "Script Context" is or where it is being displayed, unless it is a popup from the standard editor context menu. When you right click on the editor it only shows a single context menu as far as I know, unless the JS editor is doing something really, really weird or you are talking about a 'mixed' editor like the HTML editor with embedded JS, in which case it is possible for the editor to be showing different context menus based on whether it is in an HTML section or a JS section.

    >I also found both menus also shows up in web.config editor, and xml file editor which we definitely don't want.

    If they are using the standard editor context menu then it will show for basically every instance of the standard editor regardless of language. If you want your commands to be hidden in those other contexts there isn't a different menu you add them to, you simply mark them as hidden in your QueryStatus handler or do as I originally suggested with the context GUIDS.

    >Does that mean I should pass in the same GUIID:CmdID pair I found from EnableVSIPLoggin?

    Pass in where exactly?

    >We want add a menu just available for js files, while another for just available cs files.

    If they are showing the same context menu you need to hide your commands in situations where they wouldn't be applicable, there really is no other way since it is the same context menu.

    >Perhaps the way we add the menus isn't appropriate (see original code posted in the question) although it seems working for 2010. What's the right way to add context menu, one just to be used for js file, and one for just cs file in VS 2012?

    You need to hide commands when they aren't applicable, the shell can't just know when to show your commands unless you provide UI context GUIDs when you create the commands or you implement a QueryStatus handler that marks the commands as hidden.

    Ryan

    Wednesday, November 7, 2012 3:25 AM
    • I got it. I should pass in the GUIID of CS or JS that you provided in the earlier reply to AddNamedCommand2() parameter ContextUIGUIDs and I should NOT have any handler for those commands in IDTCommandTarget::QueryStatus() - I missed this when I first tried. How can I find the GUID of the "language service"?
    • I ran into another problem after I passing the GUIDs to AddNamedCommand2(). It seems working fine in either VS 2010 and 2012 at the beginning. But I found it only works ONCE. After I execute any of the command in the addin, the context menus from that addin disappeared, so are the shortcut keys assigned to them.  Even interesting is, if I went into Addin manager and DE-select this addin, then I got the context menus in the added back! So every time I use the addin, I have to DE-select the addin. Why is that? This appeared to be the same in 2010 and 2012. Has this to do with the .AddIn setting?  Here is the current setting we have:
    <?xml version="1.0" encoding="utf-16" standalone="no"?>
    <Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
      <HostApplication>
        <Name>Microsoft Visual Studio Macros</Name>
        <Version>*</Version>
      </HostApplication>
      <HostApplication>
        <Name>Microsoft Visual Studio</Name>
        <Version>*</Version>
      </HostApplication>
      <Addin>
        <FriendlyName>Our Resource Refactoring Tool</FriendlyName>
        <Description>This addin extracts strings from C# and JavaScript files into resx files.</Description>
        <Assembly>C:\Program Files (x86)\Our\v4.5\Shared Files\Our.Release.Internationalization.ResourceAddin.dll</Assembly>
        <FullClassName>Our.Release.Internationalization.ResourceAddin.Connect</FullClassName>
        <LoadBehavior>0</LoadBehavior>
        <CommandPreload>1</CommandPreload>
        <CommandLineSafe>0</CommandLineSafe>
      </Addin>
    </Extensibility>


    Wednesday, November 7, 2012 4:19 PM
  • I don't think so. Make sure your IDTCommandTarget is returning vsCommandStatusUnsupported for the commands. If you don't we will consider your command target to have handled the QueryStatus and it will never proceed to the part of the command status update code that would consider UI context GUIDs (they are the last thing considered). I suspect your QueryStatus is currently returning possibly nothing or some default value other than Unsupported.

    As for finding the language GUIDs, I don't think they are documented/published anywhere. I set a breakpoint in internal code, I don't see a public way to know those values.

    Ryan

    Thursday, November 8, 2012 4:19 AM
  • I tried to return in vsCommandStatusUnsupported in QueryStatus (but since that is 0, I think it is the default). But it still behaves the same - once execute a command in the addin, I have to uncheck the addin from the Addin Mgr.

    Reading the Remarks in http://msdn.microsoft.com/en-us/library/envdte80.commands2.addnamedcommand2.aspx. It seems parameters ContextUIGUIDs and vsCommandStatusValue are used WHILE the addin is NOT loaded. So I suspect (not so clear in the documentation), once loaded it relies on QueryStatus(). That matched with my testing result, because now it returns vsCommandStatusUnsupported, once I execute a command in the addin (that implies loading in the addin), it is no longer available until I unload it from the addin mgr.

    1. So I think I needs to also do similar kind of filtering (depending on it is a JS or CS file) in QueryStatus() and return (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled for the corresponding command. How to do that in QueryStatus()?
    2. Another question is I am not sure I understand the parameters of vsCommandStatusValue and CommandStyleFlags. Want to make sure I did use them correctly. Here is what I currently have:
    public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
    
    (.....)
    
    try
    {
    	//Add the command
    	_rightCommandForJS = _commands.AddNamedCommand2(_addInInstance, "ForJS", RightClickToolNameForJS, RightClickToolTip, true, 245, ref contextGUIDSJS, 
    		(int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, 
    		(int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, 
    		vsCommandControlType.vsCommandControlTypeButton);
    	_rightCommandForJS.Bindings = new object[] { "Text Editor::ctrl+r,ctrl+l" };
    	commandBar2 = ((CommandBars)_applicationObject.CommandBars)[contextMenuJS];
    	_rightCommandJSFull.AddControl(commandBar2, 1);
    }
    
    (.....)
    
    
    public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
    {
    	if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
    	{
    		if (commandName == "Our.ResourceAddin.Connect.ForJS")
    		{
    			//status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
    			status = vsCommandStatus.vsCommandStatusUnsupported;
    			return;
    		}
    

    The documentation in the Remarks confuses me:

    ContextUIGUIDs
        Type: System.Object[]
    
        Optional. A SafeArray of GUIDs that determines which environment 
    contexts (that is, debug mode, design mode, and so on) show the command. 
    See vsCommandDisabledFlags.
    
    vsCommandStatusValue
        Type: System.Int32
    
        Optional. Determines whether the disabled state of the command 
    is invisible or grey when you supply a ContextUIGUIDs and none are currently active.
    
    CommandStyleFlags
        Type: System.Int32
    
        Optional. Determines the state of the command when you supply 
    a ContextUIGUIDs and none of the specified contexts are currently active. 
    This parameter should always include vsCommandStatusSupported. 
    If it also includes vsCommandStatusEnabled, the command will be enabled..



    Thursday, November 8, 2012 6:05 PM
  • >Reading the Remarks in http://msdn.microsoft.com/en-us/library/envdte80.commands2.addnamedcommand2.aspx. It seems parametersContextUIGUIDs and vsCommandStatusValue are used WHILE the addin is NOT loaded. So I suspect (not so clear in the documentation), once loaded it relies on QueryStatus().

    Yeah, looking over the code that handles the AddIn QueryStatus it does appear that regardless of what you return it will return S_OK. The code that is calling this code will continue command routing if the return is anything other than S_OK, but since this code seems to hard-code the result to be S_OK that can never happen :(  This seems like a bug, I will file one but I can't see any work-around since there is no exit path from this function that returns anything other than S_OK, unless it encounters an SEH exception, and you can't easily cause that from managed code, nor do you want to :)

    I suspect your QueryStatus will need to emulate the context check that the shell would normally do here. So something like:

    QueryService for SVsShellMonitorSelection and cast it to IVsMonitorSelection. Call GetCmdUIContextCookie once for each GUID for the JS and C# language services I gave above. You only have to do this once, the cookie won't change during a session of VS. Then, in your QueryStatus handler you need to check if that cookie is active by calling IVsMonitorSelection.IsCmdUIContextActive. If that returns true (i.e. pfActive != 0) then you enable the command, otherwise you disable and hide it.

    The comments on status and 'style' flags seems wrong as well as the association of vsCommandDisabledFlags with the ContextUIGUIDs parameter, I will file a doc bug to get this fixed.

    The vsCommandDisabledFlags are the values you pass in to the vsCommandStatusValue parameter and determine the default state of your commands if no handler can be found.

    If no handler is found and you supplied context GUIDs and none of them are active your command will be hidden. If you didn't supply context GUIDs then the default visiblity will be either visible (default) or hidden if you passed in vsCommandDisabledFlagsHidden (0x20) as part of your vsCommandStatusValue value. Regardless of if you supplied context GUIDs or not your default enabled state will be enabled (default) or disabled if you supplied vsCommandDisabledFlagsGrey with your vsCommandStatusValue (these names are horrible, one of the reasons I dislike DTE so much).

    CommandStyle flags have to do with how your command is displayed in the UI. There aren't many options here and if you are being displayed on a context menu there are even fewer. The default here is complicated as it depends on where the buttons are being shown, I remember having to dig into how this flag is interpreted in 2010 and it was...not at all straightforward.  From memory I *think* it breaks down like this:

    If the button appears on a toolbar it, by default, shows only its image. This is equivalent to
    vsCommandStylePict.

    If the button appears on a menu it, by default, shows its image and its text. This is equivalent to vsCommandStylePictAndText.

    I *think* you can prevent the image from being shown on a menu by explicitly specifying vsCommandStyleText, though I am not sure why you would want to do that.

    You can make both the image and the text appear on a toolbar button by supplying vsCommandStylePictAndText.

    You current values are likely wrong, you are passing:

    vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled

    However this parameter isn't expecting flags from vsCommandStatus, it is expecting values from vsCommandDisabledFlags. The value you are passing above is 1 + 2 (supported = 1, enabled = 2). However, in the vsCommandDisabledFlags enum that maps to vsCommandDisabledFlagsGrey | vsCommandDisabledFlagsHidden, which means you command would have default values of disabled and hidden.

    Your second use of these same values is also incorrect. It is expecting values from the vsCommandStyle enum, where 3 == vsCommandStylePictAndText. That is fine, but the button placed on a toolbar would end up showing both the image and the text. You probably want something like vsCommandStylePict (the menu placement will show the text regardless).


    Thursday, November 8, 2012 7:59 PM
  • Regarding:
    > QueryService for SVsShellMonitorSelection and cast it to IVsMonitorSelection. Call GetCmdUIContextCookie once for each GUID for the JS and C# language services I gave above. You only have to do this once, the cookie won't change during a session of VS. Then, in your QueryStatus handler you need to check if that cookie is active by calling IVsMonitorSelection.IsCmdUIContextActive. If that returns true (i.e. pfActive != 0) then you enable the command, otherwise you disable and hide it.

    Can you provide a simple example and where should I put it?

    ------------------------------

    I ended don't need to use QueryService. The information Ryan provided is helpful, although it did take my a while try and error to make this works. Mainly there are either incorrect documentation, or no helpful documentation  as discussed in this thread. I marked this as answered.

    is..<input maxlength="8" size="8" />
    • Edited by evergladecw Thursday, January 24, 2013 3:30 PM Mark as answered
    Monday, November 12, 2012 5:53 PM