Archive

Archive for March, 2010

The build process of an Integration Package (part I)

March 31, 2010 Leave a comment

This series of posts describe the inner workings of how a package is build with focus on the VSCT file. In this part we’ll ravel out the parts of the build process.

When building a package in Visual Studio, the project file is used as an input on which items to include in the build. When you open up a csproj file of a integration package project (unload the project and open for edit) you can find next items which are related to the package type project:

  • EmbeddedResource Include=”VSPackage.resx” with children MergeWithCTO and ManifestResourceName
  • None Include=”source.extension.vsixmanifest” with child SubType Designer
  • VSCTCompile Include”xxx.vsct” with children ResourceName and SubType Designer
  • A PropertyGroup with children TargetRegistryRoot, RegisterOutputPackage and RegisterWithCodebase
  • An Import tag with attribute Project=”…Microsoft.VsSDK.Targets”
    The VSPackage.resx is a resource for the package ;-) . You can find references to it in the InstalledProductRegistrationAttribute on the package class.

The source.extension.vsixmanifest is the designer for the VSIX manifest, it declares what type of extensions are provided by the content in the VSIX. For more info about VSIX files see Quan To’s post on VSIX.

The VSCT file is the source for the command table (see further). The <SubType>Designer</Subtype> on the vsct file tells VS to show a View Code/View Designer group in the context menu. In both cases it opens up a xml designer.

The PropertyGroup are options of the VSIX build process and can be found in the property page of the project in the VSIX tab.

All the previous items are used during the build process. This process is declared in the import tag pointing to a build targets file : “$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\VSSDK\Microsoft.VsSDK.targets”. This file can be found typically in the “C:\Program Files\MSBuild\Microsoft\VisualStudio\v10.0\VSSDK” folder. When you open this folder you can find the Microsoft.VsSDK.targets, Microsoft.VsSDK.Common.targets files and an assembly Microsoft.VsSDK.Build.Tasks. These are the key players of the build process.

If we take a look at the tasks, we could categorize them as in beneath image:

VSSDK Build steps

  1. Check On VsSDK installation: checks if VsSDK is installed
  2. Compile VSCT: compiles the VSCT file into a CTO file (command table)
  3. Deploy VSIX: prepares for the deployment of the package
  4. Merge CTO resource: check on resources (like MenuResource) to merge into the cto file
  5. Generate Code from LEX/YACC files: the language token and lexical classes used to enable syntax coloring (see Babel)
  6. Generate PkgDef file: generates the package definition file which represents registry values that are created when an application is installed on a computer and that are referenced by the Visual Studio Shell when it starts the application.
  7. Generate Zip file: generates the Zip file to be deployed

In next post we’ll take a closer look at the tasks related to the VSCT file.

Categories: VSX Tags:

Writing to the VS ErrorList

March 23, 2010 4 comments

The Shell provides a class to gain access to the ErrorList pane : ErrorListProvider. It implements the Add/Remove of ErrorList items in the ErrorList pane. To gain access to this provider it expects a ServiceProvider in the constructor. This provider can be the GlobalService of the Package class.

An ErrorList item is a class (called ErrorTask) where you can set all needed properties to navigate to the location in the document, categorize it and show the message.

So, it’s a quite simple and natural way of showing items in the ErrorList. I’ll show you the details now.

The simpliest approach is to create a helper class that implements the IServiceProvider interface; the implementation body of this interface (GetService(Type)) is a call to the Package.GetGlobalService(serviceType). Now we enabled the construction of the ErrorListProvider with this helper class.

public class ErrorListHelper : IServiceProvider
{
   public object GetService(Type serviceType)
    {
        return Package.GetGlobalService(serviceType);
    }
}

Now we can instantiate the ErrorListProvider: it is important here to give the provider a name and a new guid. Try to avoid making a new (= new guid and name) ErrorListProvider for each call. Consider making a singleton to access the helper class, if so make it scope the package only (for example by making the class generic).

public class ErrorListHelper : IServiceProvider
{
    public ErrorListProvider GetErrorListProvider()
    {
        ErrorListProvider provider = new ErrorListProvider(this);//this implementing IServiceProvider
        provider.ProviderName = {name scoping package};
        provider.ProviderGuid = {guid scoping package};
        return provider;
    }
}

Now that we have the ErrorListProvider in place, we have to make the helper class to add and remove items from the list: this includes, creating a ErrorTask object and handle the event to navigate to the context document’s position.

Lets start taking a look on how to create a ErrorTask object:

public void Write(
            TaskCategory category,
            TaskErrorCategory errorCategory,
            string context, //used as an indicator when removing
            string text,
            string document,
            int line,
            int column)
{
    ErrorTask task = new ErrorTask();
    task.Text = text;
    task.ErrorCategory = errorCategory;
    //The task list does +1 before showing this numbers
    task.Line = line - 1;
    task.Column = column - 1;
    task.Document = document;
    task.Category = category;

    if(! string.IsNullOrEmpty(document))
    {
        //attach to the navigate event
        task.Navigate += NavigateDocument;
    }   
    GetErrorListProvider().Tasks.Add(task);//add it to the errorlistprovider

}

Now that we have the ErrorTask in place, we can take a look at the handling of the Navigate event. This is a bit of code that I have centralized in another helper class to be reusable.

void NavigateDocument(object sender, EventArgs e)
{
    Task task = sender as Task;
    if (task == null)
    {
        throw new ArgumentException("sender");
    }
    //use the helper class to handle the navigation
    ShellContext.OpenDocumentAndNavigateTo(task.Document, task.Line, task.Column);
}

What it must cover:

  • get the VS service to open the document (IVsShellOpenDocument)
  • open the document via the OpenDocumentViaProject; this gives you a pointer to the frame (IVsWindowFrame) hosting the document
  • use the frame to get the VsTextBuffer
  • get the VS service that manages the textbuffer (VsTextManager) and use it to navigate to the position in the document

Here is the peace of code that does this:

    public static void OpenDocumentAndNavigateTo(
                                    string path, int line, int column)
    {
        IVsUIShellOpenDocument openDoc =
            Package.GetGlobalService(typeof(IVsUIShellOpenDocument))
                    as IVsUIShellOpenDocument;
        if (openDoc == null)
        {
            return ;
        }
        IVsWindowFrame frame;
        Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp;
        IVsUIHierarchy hier;
        uint itemid;
        Guid logicalView = VSConstants.LOGVIEWID_Code;
        if (ErrorHandler.Failed(
            openDoc.OpenDocumentViaProject(path, ref logicalView, out sp, out hier, out itemid, out frame))
            || frame == null)
        {
            return ;
        }  
        object docData;  
        frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out docData);  

        // Get the VsTextBuffer  
        VsTextBuffer buffer = docData as VsTextBuffer;  
        if (buffer == null )  
        {  
            IVsTextBufferProvider bufferProvider = docData as IVsTextBufferProvider;  
            if (bufferProvider != null )  
            {  
                IVsTextLines lines;  
                ErrorHandler.ThrowOnFailure(bufferProvider.GetTextBuffer(out lines));  
                buffer = lines as VsTextBuffer;  
                Debug.Assert(buffer != null, "IVsTextLines does not implement IVsTextBuffer");  
                if (buffer == null)  
                {  
                   return ;  
                }  
            }  
        } 
        // Finally, perform the navigation.  
        IVsTextManager mgr = Package.GetGlobalService(typeof(VsTextManagerClass))
             as IVsTextManager;  
        if (mgr == null)  
        {  
            return;  
        }  
        mgr.NavigateToLineAndColumn(buffer, ref logicalView, line, column, line, column);  
    }

To remove the ErrorTask from the list, make use of the same Guid/Name to re-instantiate the ErrorListProvider.  The context argument in the write method can be used to remove all ErrorTasks for this context. The handling with the context is not covered here, because this post is focussed on the element to make this work.

When removing you should use the SuspendRefresh() before removing and ResumeRefresh() after the items are removed to avoid flickering.

 With all this parts paste together it should do the job, for me it did ;-)

Categories: VSX Tags: ,
Follow

Get every new post delivered to your Inbox.