MonoDevelop has a powerful extension system which can be used by developers to integrate additional functionality to the IDE (or more generally speaking, to the MonoDevelop platform).
MonoDevelop's extension system is based on a tree of extension points. An extension point is just a placeholder where add-ins can add new items which provide extra functionality. Extension points are identified with a path, which specifies its location in the tree. For example, the path: '/SharpDevelop/Workbench/MainMenu' identifies the extension point which can be used by add-ins to add items to the main menu.
Add-ins not only have to add new elements to extension points, but usually they'll have to implement some classes or interfaces. For example, an add-in which adds new menu items to the main menu may need to implement classes for handling those new commands.
The document Extension Tree Reference has a complete description of extension tree and the requirements of each extension point.
Add-ins can also add new extension points to the extension tree. For example, the SourceEditor add-in (the add-in which implements the main MonoDevelop source code editor) adds a new extension point to the tree: '/AddIns/DefaultTextEditor/EditActions'. Other add-ins can then extend the editor by implementing and registering custom edit actions.
MonoDevelop can be seen as a hierarchy of add-ins. Each add-in can add new items to extension points, and can add new extension points for other add-ins to extend.
The document 'The SharpDevelop add-in tree architecture' by Mike Krueger and Bernd Spudia has some information about the SharpDevelop add-in architecure, from which MonoDevelop was derived and further developed. This article contains several paragraphs directly taken from that document.
A MonoDevelop add-in is composed by the following files:
Most of add-ins will only have the add-in description file and one assembly implementing the extensions. Some add-ins will need additional assemblies. For example, the Stetic add-in contains a copy of the Stetic libraries.
Some add-ins will need support files. For example, the html page and css styles file used by the Welcome Page add-in are defined in the add-in as support files.
All files of an add-in can be packaged together in an .mpack file, which is just a zip file containing all files. There is a chapter in this article which explains how to generate mpack files.
This file describes the content of an add-in. It is an XML file with four sections:
Here is a more detailed description of each section:
The header is the AddIn root element of the xml file. The most important attributes are the following (all of them mandatory):
Other optional attributes are:
The Runtime element specifies the files required to run the add-in. Each file is specified in an Import element. Two kind of files can be imported: assemblies and support files. To import an assembly, just specify the relative path to the assembly in an assembly attribute. To import a support file, use the file attribute.
Here is an example of an add-in importing an assembly and some support files:
<Runtime> <Import assembly="WelcomePage.dll"/> <Import file="WelcomePage.css"/> <Import file="WelcomePage.xsl"/> <Import file="mono-bg.png"/> <Import file="mono-logo.png"/> </Runtime>
An add-in can import more than one assembly. Assemblies will be loaded in the order they are declared in the Runtime element, so if one add-in depends on another one, make sure the dependent is declared the latest.
If an add-in extends or makes use of classes implemented in another add-in, it must declare this dependency in the Dependencies element. Dependencies on external assemblies must also be declared. For example:
<Dependencies> <AddIn id="MonoDevelop.Core" version="0.10.0"/> <AddIn id="MonoDevelop.Core.Gui" version="0.10.0"/> <AddIn id="MonoDevelop.Projects" version="0.10.0"/> <AddIn id="MonoDevelop.Projects.Gui" version="0.10.0"/> <AddIn id="MonoDevelop.Ide" version="0.10.0"/> <Assembly name="nunit.framework, Version=2.2.0.0" package="nunit"/> </Dependencies>
This example add-in specifies that it depends on several MonoDevelop.* add-ins, and also that it requires the nunit-framework assembly to be available in the GAC.
Add-ins can use the Extension element to specify new elements to be added to the extension tree. The tree location where to add the elements is specified in the path attribute. For example:
<Extension path = "/SharpDevelop/Commands"> <Command id = "MonoDevelop.WelcomePage.WelcomePageCommands.ShowWelcomePage" defaultHandler = "MonoDevelop.WelcomePage.ShowWelcomePageHandler" _label = "Welcome Page" icon = "gtk-home"/> </Extension>
The format of the tree path is like the format for paths in a Unix directory tree. Inside the Extension element there are the new elements to be added. Those elements are internally referred as Codons.
Codons have attributes which specify the behaviour or state of some element of the program. Every codon can have an ID which identifies it in the tree; the codon is then inserted into the tree beneath the extension path + â/â + id. The id attribute is not mandatory, but only codons with an ID will be able to be referenced or extended by other codons.
An add-in will usually add elements to several extension points of the extension tree. See the document Extension Tree Reference for a complete description of all available extensions.
One requirement for menu items is that they need to be in some sort of order. When defining a menu, we expect the options to be shown in the order they are defined.
When there is only a single xml file defining the menu this is not a problem. Problems arise when the definitions are scattered across more than one XML file.
All codons have two common attributes which can be used to specify the placement in the tree: insertafter and insertbefore.
The insertafter attribute can be applied to a codon to specify the ID of the codon after which the it has to be inserted. For example:
<ItemSet id="MyMenu" insertafter="File" _label="My menu">
The insertafter attribute here ensures that the 'MyMenu' menu is inserted after the 'File' menu. Notice that the codon will be inserted just after the referenced codon, unless there is another add-in which defines a codon with the same insertafter reference. In this case, the codons from one add-in will be inserted after the codons of the other add-in (the order depends on which add-in was loaded first).
If you want to insert several codons after one existing codon, the insertafter attribute only needs to be applied to the first one in the list. Once the insertion point is specified for one codon, all codons following that one will be inserted after it. For example:
<Extension path = "/SharpDevelop/Workbench/MainMenu"> <ItemSet id="MyMenu1" _label="My menu 1" insertafter="File"> ... </ItemSet> <ItemSet id="MyMenu2" _label="My menu 2"> ... </ItemSet> <ItemSet id="MyMenu3" _label="My menu 3"> ... </ItemSet> </Extension>
The insertbefore attribute works like the insertafter attribute, but it forces the codon where it is specified to be inserted before the referenced codon.
Here is a complete example of an add-in description file:
<AddIn id = "MonoDevelop.WelcomePage" name = "MonoDevelop Welcome Page" author = "Scott Ellington" copyright = "MIT X11" url = "http://salmonsalvo.net/" description = "Welcome Page for MonoDevelop" category = "IDE extensions" version = "0.2.0"> <Runtime> <Import assembly="WelcomePage.dll"/> <Import file="WelcomePage.css"/> <Import file="WelcomePage.xsl"/> <Import file="mono-bg.png"/> <Import file="mono-logo.png"/> </Runtime> <Dependencies> <AddIn id="MonoDevelop.Core" version="0.10.0"/> <AddIn id="MonoDevelop.Core.Gui" version="0.10.0"/> <AddIn id="MonoDevelop.Projects" version="0.10.0"/> <AddIn id="MonoDevelop.Ide" version="0.10.0"/> </Dependencies> <Extension path = "/SharpDevelop/Commands"> <Command id = "MonoDevelop.WelcomePage.WelcomePageCommands.ShowWelcomePage" defaultHandler = "MonoDevelop.WelcomePage.ShowWelcomePageHandler" _label = "Welcome Page" icon = "gtk-home"/> </Extension> <Extension path="/SharpDevelop/Workbench/MainMenu/View"> <CommandItem id="MonoDevelop.WelcomePage.WelcomePageCommands.ShowWelcomePage" insertafter="ViewItemsSeparator2" insertbefore="MonoDevelop.Ide.Commands.ViewCommands.FullScreen" /> </Extension> <Extension path = "/MonoDevelop/IDE/StartupHandlers"> <Class class="MonoDevelop.WelcomePage.ShowWelcomePageOnStartUpHandler" /> </Extension> <Extension path = "/SharpDevelop/Dialogs/OptionsDialog"> <DialogPanel id = "WelcomPageOptions" insertafter = "ToolsOptions" _label = "Welcome Page"> <DialogPanel id = "General" _label = "General" class = "MonoDevelop.WelcomePage.WelcomePageOptionPanel" /> </DialogPanel> </Extension> </AddIn>
MonoDevelop add-ins can declare new extension points in the extension tree, to be extended by other add-ins. This section explain how to do it.
New extension points are declared in the add-in description file using the same syntax used to extend existing extension points, with some additions. For example:
<Extension path = "/MyAddIn/ContextMenu" extension-nodes="CommandItem, SeparatorItem" description="Context menu for MyAddIn."> <CommandItem id="MonoDevelop.Ide.Commands.EditCommands.Copy"/> <CommandItem id="MonoDevelop.Ide.Commands.EditCommands.Cut"/> <CommandItem id="MonoDevelop.Ide.Commands.EditCommands.Paste"/> </Extension>
In this case, this new extension point contains some elements defined in the owner add-in. In other cases this won't be needed and the extension point can be left empty.
The description attribute is an explanation of what is this extension point for.
The extension-nodes attribute specifies which elements are allowed in this extension point. MonoDevelop defines several element types or codons. A complete list can be found here. However, you can create your own codon types.
To create a new codon you need to create a class which implements ICodon (from MonoDevelop.Core.AddIns namespace). The easiest way of doing it is by subclassing the class AbstractCodon. Then, you'll need to do the following:
Here is a simple example of codon:
[Description ("A file template.")]
[CodonNameAttribute("FileTemplate")]
internal class FileTemplateCodon : AbstractCodon
{
[Description ("Resource name where the template is stored.")]
[XmlMemberAttribute("resource", IsRequired = true)]
string resource;
public string Resource {
get { return resource; }
set { resource = value; }
}
public override object BuildItem (object owner, ArrayList subItems, ConditionCollection conditions)
{
return this;
}
}
To get all elements added to an extension point you can use the GetTreeItems() method from the add-in service. for example:
object[] items = Runtime.AddInService.GetTreeItems ("/MyAddIn/ContextMenu");
Notice that the returned array will contain one object per codon found. This object will be the object returned by the BuildItem method of each codon. If you want to get the codons, and not the created items, you can use GetTreeCodons() instead.
MonoDevelop provides tools for packaging and publishing add-ins. This is what you have to do:
The URL of the main.mref file is the URL of the add-in repository you just created.
Lluis 12:55, 7 Apr 2006 (EDT)