How do I implement VSIP commands that support arguments?

This question was recently posted on the microsoft.public.vstudio.extensibility newsgroup by our good friend Mark Conway over at Micro Focus. After a bit of source code diving and investigation by our support staff, the doctor decided this might make a good blog entry.

 

This is one of those topics that just simply didn’t make it into the VSIP documentation. So I’ll do my best to write this up here, with the promise that we’ll do our best to get this information into the VSIP SDK documentation before the next release.

 

Command arguments

 

Many of you are probably pretty familiar with commands that accept arguments. For example, the ‘File.Open’ command. Try typing the following into the “Command Window (CTRL+ATL+A)” in the VS .Net IDE.

 

   File.OpenFile c:\somefile.txt

 

Notice that this particular command supports an autocompletion dropdown for the filenames as you start typing them in.

 

ALLOWPARAMS and IOleCommandTarget::Exec

 

In order for a package to enable support for a command that require arguments, you must first add the ALLOWPARAMS flag to your command, in the .CTC resource of your package.

 

When you attempt to enter an argument for a command that is flagged with ALLOWPARAMS, the shell will query your package for a parameter list. It does this by invoking IOleCommandTarget::Exec with the LOWORD of nCmdexecopt set to OLECMDEXECOPT_SHOWHELP (3), the HIWORD of nCmdexecopt set to VSCmdOptQueryParameterList (1), pvaIn set to NULL, and pvaOut pointing to an empty VARIANT ready to receive a result BSTR.

 

Note, VSCmdOptQueryParameterList enum is not defined in any of the VSIP SDK headers, but it’s a simply an enum with a value of 1. OLECMDEXECOPT_SHOWHELP is defined in docobj.h, but there’s no definition provided via the interop or VSIP helper assemblies in the current release. But looking at docobj.h, we know that it’s defined as a value of 3.

 

You can test for this in your Exec implementation using the following C++ macro:

 

 #define ISQUERYPARAMETERLIST(pvaIn, pvaOut, dwCmdExecOpt)\
   (pvaIn == NULL && pvaOut != NULL && \
    LOWORD(dwCmdExecOpt) == OLECMDEXECOPT_SHOWHELP && \
    HIWORD(dwCmdExecOpt) == 1 /* VSCmdOptQueryParameterList */)

 

 

Alternatively, if you are building a package with C#, you could implement the following helper functions to test for this condition:

 

static uint HiWord(uint val) { return ((val >> 16)&0xFFFF); }

static uint LoWord(uint val) { return (val & 0xFFFF); }

static public bool IsQueryParameterList(System.IntPtr pvaIn, System.IntPtr pvaOut, uint nCmdexecopt)

{

   if (pvaIn == IntPtr.Zero &&

   pvaOut != IntPtr.Zero &&

   LoWord(nCmdexecopt) == 3 /* OLECMDEXECOPT_SHOWHELP */ &&

   HiWord(nCmdexecopt) == 1 /* VSCmdOptQueryParameterList */)

      return true;

   else

      return false;

}

 

When ISQUERYPARAMETERLIST (or IsQueryParameterList) returns true, you will need to return a string via the pvaOut parameter describing your argument list. This string consists of a series of parameter descriptors separated by spaces. Each parameter descriptor is either ‘*’ or a series of parameter types separated by ‘|’. Each parameter type is a single character that corresponds to a type of argument that is valid for that parameter. When a parameter descriptor specifies two or more parameter types “or’d” together with ‘|’, this means that any of the specified types is valid for that parameter, and that autocompletion should present all the lists merged together.

 

If a parameter descriptor is ‘*’, this indicates zero or more occurances of the previous parameter are allowed, with the same autocompletion for each. Only the last parameter descriptor is allowed to be ‘*’, and there must be at least one preceding parameter descriptor.

 

The following is a list of the currently available parameter types:

 

   ‘~’ - No autocompletion for this parameter.

   ‘$’ - This parameter is the rest of the input line

         (no autocompletion).

   ‘a’ – An alias.

   ‘c’ – The canonical name of a command.

   ‘d’ – A filename from the file system.

   ‘p’ – The filename from a project in the current solution.

   ‘u’ – A URL.

   ‘|’ – Combines two parameter types for the same parameter.

   ‘*’ – Indicates zero or more occurrences of the previous parameter.

 

So when implementing a command such as File.Open, you would first add the ALLOWPARAMS flag to the command definition in the package’s .CTC resource. Then test for IsQueryParameterList, and pass back the string “d|p *” via the pvaOut argument.

For example:

 

protected override int Exec(ref Guid guidCmdGroup, uint cmdID,

   uint nCmdexecopt, System.IntPtr pvaIn, System.IntPtr pvaOut)

{

   int result = NativeMethods.S_OK;

   if (guidCmdGroup == GuidList.guidMyCmdSet)

   {

      switch(cmdID)

      {

         case PkgCmdIDList.cmdidMyFileOpen:

            result HandleMyFileOpen(pvaIn, ref pvaOut, nCmdexecopt);

            break;

         default:

            result = (int)Constants.OLECMDERR_E_NOTSUPPORTED;

      }

    }

   else

      result = (int)Constants.OLECMDERR_E_UNKNOWNGRP;

   return result;

}

private int HandleMyFileOpen(System.IntPtr pvaInk, ref System.pvaOut,

   uint nCmdexecopt)

{

   If (IsQueryParameterList(pvaIn, pvaOut, nCmdexecopt)

   {

      // multiple filenames from directory and/or solution items.

      string strOut = “d|p *”;

      Marshal.GetNativeVariantForObject(strOut, pvaOut);

      return NativeMethods.S_OK;

   }

   Debug.WriteLine(“MyFileOpen command executed with:”);

   object obj = Marshal.GetObjectForNativeVariant(pvaIn);

   Debug.WriteLine(obj.ToString());

   return NativeMethods.S_OK;

}

 

Below are some additional examples of query strings you can return when IsQueryParameterList returns true.

 

“p p” – Command accepts two filenames

“u d” – Command accepts one URL and one filename argument.

“u *” – Command accepts zero or more URL arguments.

 

And that completes our VSIP lesson for today. Hope that was helpful.

 

Thanks

Dr. eX