We broaden our horizons. SharpDevelop AddIns
I’ve been familiar with SharpDevelop for about a year now. On my to say the least top-end laptop, he feels excellent and at the same time manages to solve most of his tasks. But like any other development tool, it is not omnipotent. From time to time you have to turn to Visual Studio and other tools. Sometimes self-made Project Templates and File Templates help out. Sometimes - connecting console utilities through the Tools menu. But I would like something more.
SharpDevelop is, as you know, Open Source. So nothing prevents to take his code and rewrite as he pleases. But let's leave it as a last resort. SharpDevelop has a great opportunity to write plugins or AddIns for it, as the authors call them. Therefore, today we will focus on plugins and analyze how they work and how to write them. For an example we will write a simple plug-in for support of Microsoft Moles Isolation Framework .
Before that, I came across few plugin implementations. I created my version a couple of times. But this out of the blue does not suit what I saw in SharpDevelop. In it, almost everything is implemented as plugins. Editors' windows, sidebars, dialogs, support for various languages, toolbar elements, individual elements of the menu bar and context menu. If you remove all the plugins, then all that remains is the kernel for supporting plugins.
But it was not always so. The first trial versions of SharpDevelop appeared in 2000 - right after the release of the first .NET Framework. The chief architect, Mike Kruger, had never worked on a Windows platform before. Only with Linux. When he saw C #, this language seemed to him better than Java. Therefore, he decided to write an IDE for him, since at that time there was nothing similar for .NET yet.
The first version was a regular window with .NET RichTextBox. She was able to download files with code and compile them using csc.exe. Next was created its own text editor and tools for managing project files. After a while, SharpDevelop was stable enough to continue development on it.
In early 2001, the first AddIn implementation appeared. It was quite simple - you could only add items to a special item on the main menu. And at the beginning of 2002, the AddIn system acquired its final form, in which it survived to this day.
The developers set themselves a serious task - it was necessary to implement such a system of plug-ins so that it was possible to expand some plugins with the help of others. So the idea of AddIn Tree came about. AddIn Tree allows you to describe extensions using XML and connect them almost anywhere by specifying a Path.
All new functionality should declare the path along which it is placed. Path refers to a path in a tree, from the root to a specific node. For example, "/ SharpDevelop / Workbench / MainMenu". A path is just a string that allows you to find the extensions you need.
All extensions must contain a * .addin (XML) file that describes them. There may also be one or more .NET assemblies that contain extension logic. At run time, the contents of addin files are merged into a single tree. But related assemblies may not load right away.
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. With panels, the situation is similar - while the panel is closed or minimized, its code is not loaded into memory. This can significantly reduce the startup time of SharpDevelop.
Consider how Path is used to describe the extension.
Our nodes that we added will become View and Edit items in this menu. For obvious reasons, nodes located on the same path must have a unique id.
After that, each node itself becomes an element of the path. For example, "/ SharpDevelop / Workbench / MainMenu / Edit". Therefore, you can add new nodes along this path:
The Edit menu item can be implemented in one plugin, and the Copy and Paste items in another. Thus, using the paths in the tree, you can not only add your extensions, but also "expand" existing ones.
To summarize a short summary. An XML file with the AddIn root element allows us to describe our extension. In particular, what place in the program it “expands”. 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.
To do this, the creators of SharpDevelop came up with a special level of abstraction - Doozer (formerly called Codon). Doozer helps to create all kinds of objects using parameters - XML attributes. For example, MenuItemDoozer can create a menu item, and PadDoozer can create panels (such as Class View or Toolbox). Now we can update our description of the extension by adding Doozer to it.
Label is the name of the menu item that will be displayed on the screen. Class is the name of the class that 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.
At the disposal of the developer there are already many ready-made Doozers. A full list can be found in the documentation (all links at the end of the article). They all implement the IDoozer interface, and their names end in Doozer. In the XML file, the Doozer suffix is omitted.
Not all Doozers work with teams. 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 in which user settings are stored.
Now that you’ve got a basic idea of how AddIns are arranged, you can try writing your own plugin. Let me remind you that for an example we will create a plugin for using Microsoft Moles. The first thing to start is to look at the SharpDevelop window and think about where to put your plugin in it, how it will be launched and used.
We will 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. In the context menu of these assemblies there is an item "Add Moles Assembly".
With the launch of the plugin, we decided. Now you need to find out which path should be used. For this, you do not have to bury yourself in books, forums, manuals. SharpDevelop has an excellent tool - AddIn Scout (the main menu - Tools - AddIn Scout), which shows the AddIn Tree as a folder tree.
Having walked a bit through this tree, we find what we need:
"/ SharpDevelop / Pads / ProjectBrowser / ContextMenu / ReferenceNode" - this is exactly the path we need. On it, the existing context menu items have already been added: Refresh, Remove and Properties.
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 in which 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.
Editing this file is not recommended. We’ll better find out how the context menu items are described in it.
Finally, you can move on to the most interesting part - writing a plugin. First you need to download the latest version of SharpDevelop in binary form and install it. We will also need its sources. All this can be found here (stable version) or here (build server). I took with the build server.
Sources must first be compiled. To do this, it is better to use the debugbuild.bat file that comes with the kit. “Why do we need two versions of SharpDevelop?” You ask. The fact is that when the debug version is launched, in addition to the main window, the console also starts, in which you can watch the log. In particular, one can observe when and which assemblies are loaded.
Now you can open the main SharpDevelop and create a new project “SharpDevelop addin”, which is located in the “C # / SharpDevelop” section. Let's call it MolesAddIn. The template contains two files: AddInWritingHelp.txt and MolesAddIn.addin. The first contains some tips for writing plugins and a couple of links. It can be safely removed. The second will contain a description of our plugin.
In the Runtime tag, you should put all the assemblies that will be needed for your extension to work. In this case, this is the assembly of our project. Identity is the name of the extension that will be displayed in AddIn Manager (the main menu is Tools - AddIn Manager). Each extension must have one identity, and the name must be unique. As a version, you can either explicitly specify a version, for example 1.0.0, or use the assembly version, as done above.
Dependency are extensions that are needed for your AddIn to work. There may be several. SharpDevelop is the main extension that contains a lot of everything, including the Project Browser that we need. Therefore, this dependency is mandatory for all AddIns.
Path - the path by which we added the menu item. There can be several paths in one file, as in ICSharpCode.SharpDevelop.addin. Also, each menu item may contain nested menu items. The class attribute contains the name of the class that is responsible for handling the click on a menu item. For MenuItem, this class must inherit the IMenuCommand interface or the AbstractMenuCommand abstract class.
We add two links to our project: ICSharpCode.Core.dll and ICSharpCode.SharpDevelop.dll. They can be found in / path / to / sharpdevelop-source / bin after you compiled the sources. For links, you need to set the Copy To Local property to False, since our extension will launch SharpDevelop itself. Now you can add the AddMolesCommand class.
So far, the team is not doing anything useful. We only got access to the assembly, by which we right-clicked and learned its file name.
To start the extension, you need to slightly modify the properties of the MolesAddIn project. The first thing to do is change the path where the finished extension will be copied so that SharpDevelop starts it automatically. There is a special AddIns folder for this. Create a subfolder in it with the name of our extension.
Therefore, the Output Path must be set to "/ path / to / sharpdevelop-source / AddIns / MolesAddIn".
To start the debug version of SharpDevelop when you press F5, you need to change the launch method in the project properties.
Now everything is ready. Ctrl + F5 - and we have already launched the second SharpDevelop. Open the MolesAddIn project in it and right-click on any of its links.
The menu item "Add Moles Assembly" can be observed immediately after the "Properties". At this point, the MolesAddIn.dll assembly is not yet loaded, you can verify this by viewing the log in the console. After clicking on the selected menu item, a MessageBox will open, and the line "Loading AddIn MolesAddIn.dll" will appear in the log.
In AddIn Manager (Tools - AddIn Manager) you can find a record about our extension: name, version and description. It can also be seen in AddIn Scout.
This is how extension development for SharpDevelop looks. Once AddIn becomes stable enough, it can be prepared for installation in your working version of the IDE. This is done simply - all the assemblies that are needed for its operation (in our case, one - MolesAddIn.dll) along with the * .addin file are packaged in the zip archive. Further, this archive is renamed to * .sdaddin. Everything, the installation package is ready. Using AddIn Manager, you can install SharpDevelop.
Of course, this description of the AddIns system is far from complete. It only gives a general idea and helps to look at the writing of plugins by the developer.
For a more complete description, see Dissecting a C # Application: Inside SharpDevelop. In digital form, it can be downloaded for free. Since it came out, there have been no major changes in the architecture, mainly renaming. For example, Path in the book is called Extension, and Doozer - Codon. More up-to-date information can be found in the SharpDevelop distribution, in the doc / technotes folder.
PS For some reason, the source tag refuses to work for me.
SharpDevelop is, as you know, Open Source. So nothing prevents to take his code and rewrite as he pleases. But let's leave it as a last resort. SharpDevelop has a great opportunity to write plugins or AddIns for it, as the authors call them. Therefore, today we will focus on plugins and analyze how they work and how to write them. For an example we will write a simple plug-in for support of Microsoft Moles Isolation Framework .
Before that, I came across few plugin implementations. I created my version a couple of times. But this out of the blue does not suit what I saw in SharpDevelop. In it, almost everything is implemented as plugins. Editors' windows, sidebars, dialogs, support for various languages, toolbar elements, individual elements of the menu bar and context menu. If you remove all the plugins, then all that remains is the kernel for supporting plugins.
A bit of history
But it was not always so. The first trial versions of SharpDevelop appeared in 2000 - right after the release of the first .NET Framework. The chief architect, Mike Kruger, had never worked on a Windows platform before. Only with Linux. When he saw C #, this language seemed to him better than Java. Therefore, he decided to write an IDE for him, since at that time there was nothing similar for .NET yet.
The first version was a regular window with .NET RichTextBox. She was able to download files with code and compile them using csc.exe. Next was created its own text editor and tools for managing project files. After a while, SharpDevelop was stable enough to continue development on it.
In early 2001, the first AddIn implementation appeared. It was quite simple - you could only add items to a special item on the main menu. And at the beginning of 2002, the AddIn system acquired its final form, in which it survived to this day.
Addin tree
The developers set themselves a serious task - it was necessary to implement such a system of plug-ins so that it was possible to expand some plugins with the help of others. So the idea of AddIn Tree came about. AddIn Tree allows you to describe extensions using XML and connect them almost anywhere by specifying a Path.
All new functionality should declare the path along which it is placed. Path refers to a path in a tree, from the root to a specific node. For example, "/ SharpDevelop / Workbench / MainMenu". A path is just a string that allows you to find the extensions you need.
All extensions must contain a * .addin (XML) file that describes them. There may also be one or more .NET assemblies that contain extension logic. At run time, the contents of addin files are merged into a single tree. But related assemblies may not load right away.
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. With panels, the situation is similar - while the panel is closed or minimized, its code is not loaded into memory. This can significantly reduce the startup time of SharpDevelop.
Consider how Path is used to describe the extension.
Our nodes that we added will become View and Edit items in this menu. For obvious reasons, nodes located on the same path must have a unique id.
After that, each node itself becomes an element of the path. For example, "/ SharpDevelop / Workbench / MainMenu / Edit". Therefore, you can add new nodes along this path:
The Edit menu item can be implemented in one plugin, and the Copy and Paste items in another. Thus, using the paths in the tree, you can not only add your extensions, but also "expand" existing ones.
Dozer
To summarize a short summary. An XML file with the AddIn root element allows us to describe our extension. In particular, what place in the program it “expands”. 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.
To do this, the creators of SharpDevelop came up with a special level of abstraction - Doozer (formerly called Codon). Doozer helps to create all kinds of objects using parameters - XML attributes. For example, MenuItemDoozer can create a menu item, and PadDoozer can create panels (such as Class View or Toolbox). Now we can update our description of the extension by adding Doozer to it.
Label is the name of the menu item that will be displayed on the screen. Class is the name of the class that 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.
At the disposal of the developer there are already many ready-made Doozers. A full list can be found in the documentation (all links at the end of the article). They all implement the IDoozer interface, and their names end in Doozer. In the XML file, the Doozer suffix is omitted.
Not all Doozers work with teams. 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 in which user settings are stored.
Let's move on to practice
Now that you’ve got a basic idea of how AddIns are arranged, you can try writing your own plugin. Let me remind you that for an example we will create a plugin for using Microsoft Moles. The first thing to start is to look at the SharpDevelop window and think about where to put your plugin in it, how it will be launched and used.
We will 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. In the context menu of these assemblies there is an item "Add Moles Assembly".
With the launch of the plugin, we decided. Now you need to find out which path should be used. For this, you do not have to bury yourself in books, forums, manuals. SharpDevelop has an excellent tool - AddIn Scout (the main menu - Tools - AddIn Scout), which shows the AddIn Tree as a folder tree.
Having walked a bit through this tree, we find what we need:
"/ SharpDevelop / Pads / ProjectBrowser / ContextMenu / ReferenceNode" - this is exactly the path we need. On it, the existing context menu items have already been added: Refresh, Remove and Properties.
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 in which 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.
Editing this file is not recommended. We’ll better find out how the context menu items are described in it.
Finally, you can move on to the most interesting part - writing a plugin. First you need to download the latest version of SharpDevelop in binary form and install it. We will also need its sources. All this can be found here (stable version) or here (build server). I took with the build server.
Sources must first be compiled. To do this, it is better to use the debugbuild.bat file that comes with the kit. “Why do we need two versions of SharpDevelop?” You ask. The fact is that when the debug version is launched, in addition to the main window, the console also starts, in which you can watch the log. In particular, one can observe when and which assemblies are loaded.
Now you can open the main SharpDevelop and create a new project “SharpDevelop addin”, which is located in the “C # / SharpDevelop” section. Let's call it MolesAddIn. The template contains two files: AddInWritingHelp.txt and MolesAddIn.addin. The first contains some tips for writing plugins and a couple of links. It can be safely removed. The second will contain a description of our plugin.
author="OpenMinded"
copyright="GNU Lesser General Public License Version 2.1"
url=""
description="Adds support for Microsoft Moles to the projects browser">
label="Add Moles Assembly"
class="OpenMinded.MolesAddIn.AddMolesCommand"/>
In the Runtime tag, you should put all the assemblies that will be needed for your extension to work. In this case, this is the assembly of our project. Identity is the name of the extension that will be displayed in AddIn Manager (the main menu is Tools - AddIn Manager). Each extension must have one identity, and the name must be unique. As a version, you can either explicitly specify a version, for example 1.0.0, or use the assembly version, as done above.
Dependency are extensions that are needed for your AddIn to work. There may be several. SharpDevelop is the main extension that contains a lot of everything, including the Project Browser that we need. Therefore, this dependency is mandatory for all AddIns.
Path - the path by which we added the menu item. There can be several paths in one file, as in ICSharpCode.SharpDevelop.addin. Also, each menu item may contain nested menu items. The class attribute contains the name of the class that is responsible for handling the click on a menu item. For MenuItem, this class must inherit the IMenuCommand interface or the AbstractMenuCommand abstract class.
We add two links to our project: ICSharpCode.Core.dll and ICSharpCode.SharpDevelop.dll. They can be found in / path / to / sharpdevelop-source / bin after you compiled the sources. For links, you need to set the Copy To Local property to False, since our extension will launch 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()
{
// получаем доступ к выделенному элементу папки References
ReferenceNode node = Owner as ReferenceNode;
if (node != null)
{
// получаем доступ к объекту, который представляет саму ссылку
ReferenceProjectItem item = node.ReferenceProjectItem;
if (item != null) {
string fileName = item.FileName;
MessageBox.Show(fileName);
}
}
}
}
}
So far, the team is not doing anything useful. We only got access to the assembly, by which we right-clicked and learned its file name.
To start the extension, you need to slightly modify the properties of the MolesAddIn project. The first thing to do is change the path where the finished extension will be copied so that SharpDevelop starts it automatically. There is a special AddIns folder for this. Create a subfolder in it with the name of our extension.
Therefore, the Output Path must be set to "/ path / to / sharpdevelop-source / AddIns / MolesAddIn".
To start the debug version of SharpDevelop when you press F5, you need to change the launch method in the project properties.
Now everything is ready. Ctrl + F5 - and we have already launched the second SharpDevelop. Open the MolesAddIn project in it and right-click on any of its links.
The menu item "Add Moles Assembly" can be observed immediately after the "Properties". At this point, the MolesAddIn.dll assembly is not yet loaded, you can verify this by viewing the log in the console. After clicking on the selected menu item, a MessageBox will open, and the line "Loading AddIn MolesAddIn.dll" will appear in the log.
In AddIn Manager (Tools - AddIn Manager) you can find a record about our extension: name, version and description. It can also be seen in AddIn Scout.
What next?
This is how extension development for SharpDevelop looks. Once AddIn becomes stable enough, it can be prepared for installation in your working version of the IDE. This is done simply - all the assemblies that are needed for its operation (in our case, one - MolesAddIn.dll) along with the * .addin file are packaged in the zip archive. Further, this archive is renamed to * .sdaddin. Everything, the installation package is ready. Using AddIn Manager, you can install SharpDevelop.
Of course, this description of the AddIns system is far from complete. It only gives a general idea and helps to look at the writing of plugins by the developer.
For a more complete description, see Dissecting a C # Application: Inside SharpDevelop. In digital form, it can be downloaded for free. Since it came out, there have been no major changes in the architecture, mainly renaming. For example, Path in the book is called Extension, and Doozer - Codon. More up-to-date information can be found in the SharpDevelop distribution, in the doc / technotes folder.
PS For some reason, the source tag refuses to work for me.