Current Entries | Archives   RSS

 


A first look at a rather useful display in Visual Lint

Friday 28th January, 2005


Every so often in the development of a product you have one of those wonderful "Yes!!!" moments., and we are certainly no exception.

The Visual Lint Warning Lookup Display.
The display allows the user to view details of any warning by category.

Our most recent one came on Thursday evening, when one of the displays we'd envisaged for Visual Lint came together and began to show its full potential.

It's actually a very simple idea, and closely related to the concept of "Dynamic Help" introduced in Visual Studio .NET - a tool window which displays information on a specified warning. This is particularly useful since many PC-lint warnings which are likely to be unfamiliar, and repeatedly referring to the manual to find out what lint "error xxx" means is time consuming and unproductive.

When we've needed to do this we've tended to load up the PC-lint manual (a PDF file) and used the "find" function to find information on the warning we were seeing. However, even with the ability to view the manual within the Visual Studio IDE provided by the add-in, it's a bit of a laborious process.

A short while ago we had an idea of how to overcome that, and it all starts with a text file called msg.txt which is supplied with the PC-lint installation. It struck us that if we could parse that file and convert it to a searchable database, we could write a toolwindow which allowed the user to look up information on a specified warning, without recourse to the manual. Beth's speciality is database design, so this was an ideal opportunity to bring her skills to bear on the project too. While I designed and implemented the user interface, she did the same for the parser and database which are critical to the operation of the display.

We started putting the two together earlier this week, and the result is everything we'd hoped for, as I hope you can see from the images on the right.

Using this display a developer can either browse warnings by category (the section numbers for which correspond to those in the PC-lint manual), or enter a warning number manually and look up its definition.

That's pretty useful in itself, but what if we could integrate it in such a way that double clicking on a warning in the Output Window would not only jump to the corresponding line in the source file, but also show the definition of the warning in our toolwindow? Not that would definitely be useful!

Time to do some research. However, although we found we could intercept commands such as "Edit.Goto Next Location" (F8 by default) simply enough by implementing the BeforeExecute() and AfterExecute() command event handlers, we just couldn't find any way to intercept double click events in the Output Window, and were coming to the conclusion that we'd have to walk the window hierarchy (using Win32 directly) and hook or subclass the window.

A query I posted to the vsnetaddin Yahoo Group and the microsoft.public.vstudio.extensibility newsgroup (on msnews.microsoft.com if you're looking for it) describes the problem and how we thought we might have to address it:

We're developing an add-in which needs to catch events in an associated pane in the output window, which contains Lint warning information relating to the code. As you'd expect, double clicking on a line in the pane will jump to the corresponding line in the code.

So far so good. However, we also have a corresponding toolwindow which displays details of a selectable warning. We'd like to link this to the output window so that double clicking on a warning in the output window will not only open the corresponding line of the source, but also show details of the corresponding warning in the tool window.

Studying the automation model, catching the command events BeforeExecute() and AfterExecute() looks like it will do the trick. A little ferreting around in the debugger reveals that the "Go to Error/Tag" command on the context menu of the output window is nameless, but has GUID {5EFC7975-14BC-11CF-9B2B-00AA00573819} and ID 319. There are other commands which have the same effect (e.g."Goto Previous Location" and "Goto Next Location"), but doubtless we can catch them the same way.

However, we haven't been able to find a way to catch the most common way the user will do this - double clicking on a line in the output window, since no command seems to be generated in this case.

Does anyone know a way to catch this event without resorting to finding the HWND of the output window pane (presumably using EnumChildWindows and FindWindowEx) and subclassing it?

Although we did not get any replies to that query, we stumbled on an alternative approach within a couple of days - and in the process also learnt a little more about how to take advantage of the VS.NET extensibility model.

What we came to realise was that we could in fact implement the behaviour we wanted by using the WindowActivated() event instead. If the window being activated is a code window, that being deactivated is the Lint Analysis pane in the output window and the text at the current line in the Output Window represents a clickable warning message, the chances are that the user has double clicked.

The code fragment below illustrates this:

void CAnalysisScheduler::WindowActivated(EnvDTE::Window* pGotFocus, EnvDTE::Window* pLostFocus)
{
    if (NULL == m_pWindowManager)
    {
        ATLASSERT(false);
        return;
    }

    try
    {
        EnvDTE::WindowPtr ptrActivatedWindow(pGotFocus);
        IfNullIssueError(ptrActivatedWindow);

        EnvDTE::DocumentPtr ptrDocument = ptrActivatedWindow->GetDocument();
        IfNullIssueError(ptrDocument);

        EnvDTE::WindowPtr ptrDeactivatedWindow(pLostFocus);
        IfNullIssueError(ptrDeactivatedWindow);

        _bstr_t bsPathName = ptrDocument->GetFullName();

        EnvDTE::vsWindowType eWindowType = ptrDeactivatedWindow->GetType();

        if ( (EnvDTE::vsext_wt_OutPutWindow == eWindowType) && (bsPathName.length() > 0) )
        {
            // If the window being activated is a code editor and the deactivated window
            // is the output window, check to see if the active pane within it is the
            // "Lint Analysis Results" pane. If so, the user has most possibly double clicked
            // on a warning and we need to show the corresponding information in the
            // Warning Lookup Display

            EnvDTE::WindowPtr ptrOutputWindow = ptrDeactivatedWindow;
            IfNullIssueError(ptrOutputWindow);

            EnvDTE::OutputWindowPtr ptrOutputWindowObject = ptrOutputWindow->GetObject();
            IfNullIssueError(ptrOutputWindowObject);

            EnvDTE::OutputWindowPanePtr ptrOutputWindowPane =
                ptrOutputWindowObject->GetActivePane();

            if (m_pWindowManager->IsAnalysisResultsPane(ptrOutputWindowPane) )
            {
                m_pWindowManager->SynchroniseWarningLookupDisplay(m_ptrLintWarningLookupCtrl,
                                                                  ptrOutputWindowPane);
            }
        }
    }
    catch (const _com_error& e)
    {
         .
         .
    }
}

With corresponding implementations of the aforementioned BeforeExecute() and AfterExecute() handlers, the contents of the new display now stays synchronised with the selected warning, regardless of whether the user double clicks on the Output Window, or cycles through the warnings using commands or hotkeys.

It works like a charm.

Posted by Anna at 8:34pm | Get Permalink