Django Editor in VS 2010 – Part 0 (Background)

July 29th, 2009

Look here for complete source

The purpose of this post series is to give you a concrete proof (with code samples) of how easy it is to build custom text editors with Visual Studio 2010. But before we dive into the details please bear with me as I give you some background.

Ok we've got NDjango - nice language, everybody's happy with it, just loves it, fine. Now, how about an editor for ndjango templates? NDjango is django ported to .NET so, naturally, the editor is supposed to be integrated with Visual Studio.

What would be necessary here is a text editor with syntax highlighting, error reporting and code completion to support django syntax in the templates built for .NET applications. Django templates can be used to generate text files of various types - including html, json, xml to name a few. So all features of the django template editor should work in addition to the features already provided, i.e. for an html file syntax highlighting and code completion should work both for django as well as for the existing html.

Visual Studio 2008 provides variety of ways for custom editor  integration. It also allows building custom editors on top of the generic text editor. While building the editor this way is certainly doable, it is really heavy lifting. You need to implement your own language service with your own implementation of IVsLanguageInfo, IVsCompletionSet, IVsLanguageDebugInfo, - the list goes on and on.

And this is not the worst of it. After you are done with all the plumbing registering and have your nice editor for your nice language, you still have a problem. The Visual Studio Core Editor used as a starting point is a bare bones editor with not much beyond simple cut copy paste. If you need more - you are the one to implement it. If you need html support (at least I do) - build it.

Wouldn't it be nice if instead of building all this stuff from scratch I could piggy back on the existing editors? - Oh well...

As I was gathering my courage before I plunge head first into the task of building the NDjango Language service somebody told me to have a look at Visual Studio 2010 beta 1. So I did and now I am writing this still in awe at how much simpler it is.  

The simplicity comes in two flavors. First of all now rather than writing your own you indeed amend the behavior of the existing editors providing simple plug-ins for the functions you want to affect - i.e. coloring your tags. Secondly, thanks to MEF, the plumbing necessary for your plugins to play nice with Visual Studio is virtually non-existing.

OK. Now it is time to stop blabbering and start dig into specifics. In the next post I will start with the colorization code for the NDjango template editor.

Injecting functionality into Visual Studio projects, or if neither of the two options are good, go for the third one

July 28th, 2009

For some time now I was dancing around a question: How do I inject additional functionality into a Visual Studio project.

One option would be to build a VSPackage which will be always loaded and will be watching every move you make and interfere when it thinks the time is right. Another option is to implement a custom project type so that these extra commands are only available for the projects of this type.

I was going back and forth between these 2 options. The first one seems to be too intrusive, the second one is too much work - even if you use project flavoring, you still need to create a flavor for every project you intend to support. It also makes it difficult to convert existing projects to bistro projects.

It looks like the right chice is neither and all this time it was right under my nose. The option I am going to go with is based on the VsProjectStartupServices interface. This interface allows to add to the project file a GUID of a global service to be started when the project is opened.

Here is how this approach works:

First, in your package definition you have to declare the service as a global service:

// This attribute registers a service used to
// bootstrap the package for converted projects
[ProvideService(typeof(SProjectManager))]

The next step is to actually instantiate and register the object implementing the service:

protected override void Initialize()
{
   base.Initialize();
   solutionEvents = new SolutionEventSinks();
   projectManager = new ProjectManager(this, solutionEvents);
   IServiceContainer container = this as IServiceContainer;
   container.AddService(typeof(SProjectManager), projectManager, true);
 
    ...
 
}

Do not miss the last parameter to the AddService method. Setting it to true promotes the service registration. In other words makes it global.

Based on this registartion a call to the Package.GetGlobalService(typeof(SProjectManager)) will always return an instance of the ProjectManager. It means that if the package is not active yet, Visual Studio will load the package first, giving it a chance to instantiate and Register the ProjectManager service. This is what will happen if any project with a reference to the ProjectManager service will be opened in Visual Studio.

Now inserting such a reference into a project is pretty simple too. All what is necessary is a command executing the following:

internal void Convert()
{
   object startupService;
 
   ErrorHandler.ThrowOnFailure(
         hierarchy.GetProperty(
                VSConstants.VSITEMID_ROOT,
                (int)__VSHPROPID.VSHPROPID_StartupServices,
                out startupService));
 
   Guid guid = new Guid(Guids.guidProjectManager);
 
   ErrorHandler.ThrowOnFailure(
        ((IVsProjectStartupServices)startupService)
                .AddStartupService(ref guid));
}

Such command can be placed somewhere in the menu and configured in such a way that the command is visible even if the package is not loaded.