3

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.

Jon Senchyna
  • 7,867
  • 2
  • 26
  • 46
  • Is your extension currently open source? I'm curious which language service framework, if any, you are using for the other features. – Sam Harwell Feb 24 '14 at 22:48
  • It is an internal project I am working on at my company to add support for an internal scripting language. I created a `VSPackage` from scratch and used ANTLR4 for the parser/lexer. Syntax highlighting is done by my implementation of the `IScanner` interface. Error checking is performed in implementations of the `IParseTreeListener` interface, with hooks to an implementation of the `IErrorHandler` interface for displaying them in the GUI. "Go To Defintion" is in an implementation of the `AuthoringScope` class (and I believe code-completion is handled there as well). – Jon Senchyna Feb 25 '14 at 16:17

1 Answers1

6

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.

Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
  • I will give this a try tomorrow and let you know how it turns out. Thank you for such a detailed answer! I was not expecting any answers at all this quickly. – Jon Senchyna Feb 25 '14 at 01:54
  • @JonSenchyna Please let me know if I made an error, rather than edit this post. My library uses extension methods in many places and sometimes I miss them. I added the `DoSearch` extension method which handles the array issue and also allows passing the `Guid` by value instead of with `ref`. – Sam Harwell Feb 25 '14 at 13:48
  • My apologies; thanks for cleaning that up. Do you have any good references or examples on implementing the `IVsSimpleObjectList2` interface? I'm making quite a bit of progress, but it's been a lot of trial-and-error. – Jon Senchyna Feb 25 '14 at 16:06
  • Actually, I think I found a good starting point. Thanks again for the help. – Jon Senchyna Feb 25 '14 at 16:08
  • Ah, `dwCustom` was the missing piece for me. I was starting to lose hope that there was a way to populate the list manually :-) Thanks for this super detailed answer! (I'm used to much more sparse documentation on VS guts.) – Cameron Mar 14 '14 at 21:06
  • Is there a way to filter down to the selected file, or even certain extensions? I have a language service for a proprietary scripting language. We typically have some of these scripts included as embedded resources in normal C# projects. Finding references takes forever when I have the solution loaded, but is very quick when I only have the file open. – Jon Senchyna Aug 07 '18 at 19:20