Home .NET Expanding our horizons. SharpDevelop AddIns

Expanding our horizons. SharpDevelop AddIns

by admin

I’ve been familiar with SharpDevelop for probably about a year now. On my not-so-high-end laptop it feels great and manages to solve most of the tasks I have set for it. But like any other development tool, it’s not omnipotent. From time to time we have to turn to Visual Studio and other tools. Sometimes it is helped out by self-written Project Templates and File Templates. Sometimes I connect console tools through the Tools menu. But I’d like to have something more.
SharpDevelop is known to be Open Source. So nothing prevents you from taking its code and rewriting it however you want. But let’s leave that to an extreme case. SharpDevelop has a great feature to write plugins or AddIns, as the developers call them. So today we will focus on plugins and explain how they work and how to write them. As an example, let’s write a simple plugin to support Microsoft Moles Isolation Framework
I’ve encountered few plugin implementations before. I created my own version a couple of times. But it’s nowhere near as good as what I saw in SharpDevelop. Almost everything is implemented as plugins in SharpDevelop. Editor windows, sidebars, dialogs, language support, toolbar elements, separate menu bar and context menu items. If you remove all the plugins, all that’s left is the core for plugin support.

A bit of history

But it wasn’t always like this. The first trial versions of SharpDevelop appeared in 2000 – just after the first .NET Framework was released. The main architect, Mike Kruger, had never worked on the Windows platform before. Only with Linux. When he saw C#, this language seemed to him better than Java. So he decided to write an IDE for it, since at that time there was nothing like it for .NET.
The first version was a normal .NET RichTextBox. It could load code files and compile them with csc.exe. Then it created its own text editor and tools to manage project files. After a while SharpDevelop was stable enough to continue development on it itself.
In early 2001, the first implementation of AddInappeared. It was quite simple – you could only add items to a special main menu item. And in the beginning of 2002 the AddInsystem got its final form in which it survived until today.

AddInTree

The developers have set themselves a serious task – it was necessary to implement such a system of plugins, so that it would be possible to expand some plugins with the help of other plugins. That’s how the idea of AddInTree came about. AddIn Tree allows you to describe extensions using XML and plug them in almost anywhere by specifying the Path.
All new functionality must declare the path in which it is placed. Pathrefers to the path in the tree, from the root to a certain node. For example – "/SharpDevelop/Workbench/MainMenu". The path is just a string that allows you to find the extensions you want.
All extensions must contain an *.addin (XML) file that describes them. There can also be one or more .NET assemblies which contain the extension logic. At runtime the contents of the addin-files are merged into one tree. But linked assemblies may not be loaded immediately.
For example, the path "/SharpDevelop/Workbench/MainMenu" contains all the main menu items. The tree is used when rendering this menu, and the code is loaded only when one of its items is selected. The situation with panels is similar: while a panel is closed or minimized, its code is not loaded into memory. This allows you to significantly reduce the startup time of SharpDevelop.
Let’s look at how Pathis used when describing an extension.

< AddIn >
<!-- some stuff -->
< Path name ="/SharpDevelop/Workbench/MainMenu" >
<!-- node with id="View" -->
<!-- node with id="Edit"-->
</ Path >
</ AddIn >

Our nodes that we have added will become View and Edit items in this menu. For obvious reasons, nodes along the same path must have a unique id.
After that, each node itself becomes a path element. For example – "/SharpDevelop/Workbench/MainMenu/Edit". Consequently, new nodes can be added along this path :

< Path name ="/SharpDevelop/Workbench/MainMenu/Edit" >
<!-- node with id="Copy" -->
<!-- node with id="Paste" -->
</ Path >

The Edit menu item can be implemented in one plugin, and the Copy and Paste items in another. Thus, using paths in the tree, you can not only add your extensions, but also "extend" existing ones.

Doozer

To summarize a bit. An XML file with an AddIn root element allows us to describe our extension. In particular, which place in the program it "extends". The code itself is stored in a separate .NET assembly. So now we need to describe in the XML file how to run this code.
That’s why the creators of SharpDevelop invented a special abstraction layer called Doozer (formerly called Codon). Doozer helps to create all sorts of objects using parameters – XML attributes. For example MenuItemDoozer can create menu items and PadDoozer can create panels (like Class View or Toolbox). We can now update our extension description by adding Doozer.

< AddIn >
<!-- some stuff -->
< Path name ="/SharpDevelop/Workbench/MainMenu" >
< MenuItem id ="Edit"
label ="Edit"
class ="Namespace.EditCommandImplementation" />
</ Path >
</ AddIn >

Label is the nameof the menu item that will be displayed on the screen. Class is the name of the classthat implements the IMenuCommand interface (inherited from ICommand). When you click on a menu item, an object of this class is created and its Run method is called.
There are many ready-made Doozers at the developer’s disposal. A complete list can be found in the documentation (all references are at the end of the article). They all implement the IDoozer interface and their names end with Doozer. In the XML file the Doozer suffix is omitted.
Not all Doozers work with commands. For example, ClassDoozer simply creates a class object using the default constructor and inserts it into the tree. And CustomPropertyDoozer creates one of the properties where custom settings are stored.

Moving on to practice

Now that you have a basic understanding of how AddIns work, you can try writing your own plugin. Let me remind you that for the example, we will be creating a plugin to use Microsoft Moles. The first thing to start with is to look at the SharpDevelop window and think about where to put your plugin in it and how it will run and be used.
Let’s not be original and do it by analogy with Visual Studio. If you remember, to create Moles there, you need to select the desired assembly in the References folder, in the Projects panel. The context menu of these assemblies has the item "Add Moles Assembly".
We are done running the plugin. Now we need to know which path to use. You don’t have to browse through books, forums and manuals to do this. SharpDevelop has an excellent tool – AddIn Scout (main menu – Tools – AddIn Scout) which shows AddIn Tree as a folder tree.
After wandering around this tree a bit, we find what we need:
Expanding our horizons. SharpDevelop AddIns
"/SharpDevelop/Pads/ProjectBrowser/ContextMenu/ReferenceNode" is exactly the path we need. Existing context menu items Refresh, Remove, and Properties have already been added to it.
The FileName link points to the path of the .addin file in which the selected extension is defined. In this case it is ICSharpCode.SharpDevelop.addin, the main file where the main extensions are defined. It is quite large, 2171 lines. If you click on the link, this file will open in the built-in XML editor.
It is not recommended to edit this file. It is better to find how the context menu items are described in it.

< Path name ="/SharpDevelop/Pads/ProjectBrowser/ContextMenu/ReferenceNode" >
< MenuItem id ="RefreshReference"
icon ="Icons.16x16.BrowserRefresh"
label ="${res:AddIns.HtmlHelp2.Refresh}"
class ="ICSharpCode.SharpDevelop.Project.Commands.RefreshReference" />
< MenuItem id ="Remove"
label ="${res:Global.RemoveButtonText}"
icon ="Icons.16x16.DeleteIcon"
class ="ICSharpCode.SharpDevelop.Project.Commands.DeleteProjectBrowserNode" />
< MenuItem id ="RemoveSeparator" type ="Separator" />
< MenuItem id ="Properties"
icon ="Icons.16x16.PropertiesIcon"
label ="${res:XML.MainMenu.FormatMenu.ShowProperties}"
class ="ICSharpCode.SharpDevelop.Project.Commands.ShowPropertiesForNode" />
</ Path >

Finally, we can move on to the most interesting part – writing the plugin. First we need to download the latest versionof SharpDevelop in binary form and install it. We will also need its source code. You can find all this here (stable version)or here (build server). I took it from the build server.
The sources have to be compiled first. To do this, it is better to use the debugbuild.bat file that comes with it. "Why do we need two versions of SharpDevelop?" – you may ask. The point is that when you start the debug-version, apart from the main window you also start the console where you can watch the log. Particularly there you can observe when and what builds are loaded.
Now you can open the main SharpDevelop and create a new project "SharpDevelop addin", which is located under "C#/SharpDevelop". Let’s call it MolesAddIn. The template contains two files: AddInWritingHelp.txt and MolesAddIn.addin. The first contains a few tips for writing plugins and a couple of links. You can safely remove it. The second will contain a descriptionof our plugin.

< AddIn name ="MolesAddIn"
author ="OpenMinded"
copyright ="GNU Lesser General Public License Version 2.1"
url =""
description ="Adds support for Microsoft Moles to the projects browser" >
< Runtime >
< Import assembly ="MolesAddIn.dll" />
</ Runtime >
< Manifest >
< Identity name ="OpenMinded.MolesAddIn" version ="@MolesAddIn.dll" />
< Dependency addin ="SharpDevelop" version ="4.0" />
</ Manifest >
< Path name ="/SharpDevelop/Pads/ProjectBrowser/ContextMenu/ReferenceNode" >
< MenuItem id ="AddMolesAssembly"
label ="Add Moles Assembly"
class ="OpenMinded.MolesAddIn.AddMolesCommand" />
</ Path >
</ AddIn >

In the Runtime tag you should put all the assemblies that you will need to run your extension. In this case it is the assembly of our project. Identity is the name of the extension that will be displayed in the AddIn Manager (main menu – Tools – AddIn Manager). Each extension must have the same identity and the name must be unique. As a version, you can either explicitly specify the version, for example 1.0.0, or use the build version, asdone above.
Dependencies are extensions that are needed for your AddIn to work. There can be several of them. SharpDevelop is the main extension that contains a lot of stuff, including the Project Browser, which we need. Therefore, this dependency is mandatory for all AddIns.
Path – the path where we added the menu item. There can be several paths in one file, as in ICSharpCode.SharpDevelop.addin. Also each menu item can contain submenu items. The classattribute contains the name of the class, which is responsible for handling clicking on a menu item. For MenuItem, this class must inherit the IMenuCommand interface or the abstract AbstractMenuCommand class.
Let’s add two references to our project: ICSharpCode.Core.dll and ICSharpCode.SharpDevelop.dll. You can find them in /path/to/sharpdevelop-source/bin after you compiled the sources. You should set the Copy To Local property to False for the links, because our extension will run SharpDevelop itself. Now you can add the AddMolesCommand class.

using System;
using System.Windows.Forms;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Project;
namespace OpenMinded.MolesAddIn
{
public class AddMolesCommand : AbstractMenuCommand
{
public override void Run()
{
// access the selected element of the References folder
ReferenceNode node = Owner as ReferenceNode;
if (node != null )
{
// access the object that represents the link itself
ReferenceProjectItem item = node.ReferenceProjectItem;
if (item != null ) {
string fileName = item.FileName;
MessageBox.Show(fileName);
}
}
}
}
}

So far, the command isn’t doing anything useful. We just accessed an assembly that we right clicked on and found out its filename.
To run the extension, you need to edit the properties of the MolesAddIn project a bit. The first thing to do is to change the path where the ready-made extension will be copied, so that SharpDevelop will run it automatically. There is a special folder AddIns for this purpose. Create there a subfolder with the name of our extension.
So Output Path should be set to "/path/to/sharpdevelop-source/AddIns/MolesAddIn".
Expanding our horizons. SharpDevelop AddIns
To start a debug-version of SharpDevelop when you press F5, you need to change the startup method in the project properties.
Expanding our horizons. SharpDevelop AddIns
Now we’re all set. Ctrl+F5 – and the second SharpDevelop is already running. Open the MolesAddIn project there and right-click on any of its links.
Expanding our horizons. SharpDevelop AddIns
The menu item "Add Moles Assembly" can be observed right after "Properties". At this point the MolesAddIn.dll assembly is not loaded yet, you can verify this by viewing the log in the console. After you click on the selected menu item, MessageBox will open, and the log will show the line "LoadingIn AddAddIn.dll".
In the AddIn Manager (Tools – AddIn Manager) you can find our extension’s entry: name, version and description. Also you can see it in AddIn Scout.

What’s next?

This is what developing extensions for SharpDevelop looks like. After AddIn becomes stable enough, you can prepare it for installation into your working version of the IDE. It’s easy to do – all the assemblies that are required for it to work (in our case there is one – MolesAddIn.dll) together with the *.addin file are packed into the zip archive. Then this archive is renamed to *.sdaddin. That’s it, the installation package is ready. Using the AddIn Manager it can be installed by SharpDevelop.
Of course, this description of the AddIns system is far from complete. It only gives a general idea and helps to look at writing plugins from a developer’s point of view.
A more complete description can be found in the book Dissecting a C# Application: Inside SharpDevelop It can be downloaded digitally for free. Since it came out, there have been no major changes in architecture, mostly renaming. For example, Path is called Extension in the book, and Doozer is called Codon. More current information can be found in the SharpDevelop distribution, in the doc/technotes folder.
P. S. For some reason the source tag refuses to work for me.

You may also like