Question

I'm implementing a Visual Studio Language Service for a custom scripting language. I've managed to implement syntax highlighting, error checking, code completion, and "Go To Definition". I am having trouble figuring out how to hook in to the "Find All References" menu option (or even get it to display at this point).

Can anyone point me to a useful resource for implementing "Find All References" functionality in Visual Studio for a custom language? I've tried Googling for any information on it, but I can't seem to find anything.

Was it helpful?

Solution

First of all, there are multiple locations where Find All References can be invoked. The primary ones are:

  1. By right clicking on a node in Class View.
  2. By right clicking within the text editor.

Others include:

  1. The Call Hierarchy

Getting Started

In the ideal implementation, you'll have an implementation of IVsSimpleLibrary2 which integrates support for your language into the Class View and Object Browser windows. The implementation of Find All References centers around the IVsFindSymbol interface, which is provided by Visual Studio. Your code handles the relevant searches in the implementation of IVsSimpleLibrary2.GetList2.

Supporting right clicking on a node in Class View

  1. Make sure your library capabilities includes _LIB_FLAGS2.LF_SUPPORTSLISTREFERENCES.

  2. In your handler for IVsSimpleLibrary2.GetList2, you are interested in the case where all of the following are true.

    1. pobSrch is a non-null array of length 1. I'll assume the first element is assigned to the local variable criteria for the remainder of these conditions.
    2. criteria.eSrchType == VSOBSEARCHTYPE.SO_ENTIREWORD
    3. criteria.grfOptions has the flag _VSOBSEARCHOPTIONS.VSOBSO_LOOKINREFS
    4. criteria.grfOptions has the flag _VSOBSEARCHOPTIONS.VSOBSO_CASESENSITIVE
  3. When the above conditions are met, return an IVsSimpleObjectList2 implementation whose children are lazily computed results of a Find All References command.

Supporting the Text Editor Command

  1. In your ViewFilter.QueryCommandStatus implementation, when guidCmdGroup == VSConstants.GUID_VSStandardCommandSet97 and nCmdId == VSStd97CmdID.FindReferences you need to return OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_ENABLED.

    • In Visual Studio 2005, note that nCmdId will be VSStd2KCmdID.FindReferences but the guidCmdGroup will be the same as mentioned before. This mismatch was corrected starting in Visual Studio 2008, after which point VSStd2KCmdID.FindReferences was no longer used.
  2. Override ViewFilter.HandlePreExec for the case of the command GUID and ID listed above, and execute the following code for that case:

    HandleFindReferences();
    return true;
    
  3. Add the following extension method class:

    public static class IVsFindSymbolExtensions
    {
        public static void DoSearch(this IVsFindSymbol findSymbol, Guid symbolScope, VSOBSEARCHCRITERIA2 criteria)
        {
            if (findSymbol == null)
                throw new ArgumentNullException("findSymbol");
    
            VSOBSEARCHCRITERIA2[] criteriaArray = { criteria };
            ErrorHandler.ThrowOnFailure(findSymbol.DoSearch(ref symbolScope, criteriaArray));
        }
    }
    
  4. Add the following method to your ViewFilter class:

    public virtual void HandleFindReferences()
    {
        int line;
        int col;
    
        // Get the caret position
        ErrorHandler.ThrowOnFailure( TextView.GetCaretPos( out line, out col ) );
    
        // Get the tip text at that location. 
        Source.BeginParse(line, col, new TokenInfo(), ParseReason.Autos, TextView, HandleFindReferencesResponse);
    }
    
    // this can be any constant value, it's just used in the next step.
    public const int FindReferencesResults = 100;
    
    void HandleFindReferencesResponse( ParseRequest req )
    {
        if ( req == null )
            return;
    
        // make sure the caret hasn't moved
        int line;
        int col;
        ErrorHandler.ThrowOnFailure( TextView.GetCaretPos( out line, out col ) );
        if ( req.Line != line || req.Col != col )
            return;
    
        IVsFindSymbol findSymbol = CodeWindowManager.LanguageService.GetService(typeof(SVsObjectSearch)) as IVsFindSymbol;
        if ( findSymbol == null )
            return;
    
        // TODO: calculate references as an IEnumerable<IVsSimpleObjectList2>
    
        // TODO: set the results on the IVsSimpleLibrary2 (used as described below)
    
        VSOBSEARCHCRITERIA2 criteria =
            new VSOBSEARCHCRITERIA2()
            {
                dwCustom = FindReferencesResults,
                eSrchType = VSOBSEARCHTYPE.SO_ENTIREWORD,
                grfOptions = (uint)_VSOBSEARCHOPTIONS2.VSOBSO_LISTREFERENCES,
                pIVsNavInfo = null,
                szName = "Find All References"
            };
    
        findSymbol.DoSearch(new Guid(SymbolScopeGuids80.All), criteria);
    }
    
  5. Update your implementation of IVsSimpleLibrary2.GetList2. When the search criteria's dwCustom value is set to FindReferencesResults, rather than compute the results of a Find All References command on a Class View or Object Browser node, you only need to return an IVsSimpleObjectList2 that wraps the results previously calculated by your HandleFindReferencesResponse method.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top