SharpDevelop Community

Get your problems solved!
Welcome to SharpDevelop Community Sign in | Join | Help
in Search

Matt Ward

  • WiX 3.0 AddIn for SharpDevelop 2.2

    If you want to use WiX 3.0 with SharpDevelop 2.2 now you can. SharpDevelop 2.2 will still ship with WiX 2.0 support however the WiX 3.0 addin has been backported. This WiX 3.0 addin for SharpDevelop 2.2 can be downloaded at the end of this post. It was built and tested using WiX 3.0.4714 which is the most recent release at the current time.

    Installing

    The simplest way to use the addin is as follows.

    1. Download the wix3-binaries.zip file from SourceForge.
    2. In SharpDevelop's WiX addin folder SharpDevelop\2.2\AddIns\AddIns\BackendBindings\WixBinding rename the original WixBinding.addin file to WixBinding.addin-bak.
    3. Copy the contents of the bin folder in the Wix3Binding.zip file to SharpDevelop\2.2\AddIns\AddIns\BackendBindings\Wix3Binding.
    4. Copy the contents of the doc folder in the wix3-binaries.zip file to the SharpDevelop\2.2\data\schemas folder. This updates the WiX schemas for use in the XML editor.
    5. Rename the folder SharpDevelop\2.2\Tools\Wix to SharpDevelop\2.2\Tools\Wix-bak
    6. Copy the contents of the wix3-binaries.zip file to the SharpDevelop\2.2\Tools\Wix folder.
    7. Restart SharpDevelop.

    Updating to Newer WiX Versions

    1. Download the wix3-binaries.zip file from wix.sf.net
    2. Copy the contents of the wix-binaries.zip file to the folder SharpDevelop\2.2\Tools\Wix.
    3. Copy the contents of the doc folder to the SharpDevelop\2.2\data\schemas folder.
    4. If there are any problems try re-compiling the source code and check the unit tests still work.

    Compiling the Source Code

    1. Copy the contents of the Wix3Binding zip file into the folder SharpDevelop\2.2\src\AddIns\BackendBindings\Wix3Binding.
    2. Run msbuild WixBinding.sln.

    Download Wix3Binding.zip

    Posted Nov 18 2008, 07:53 PM by MattWard with no comments
    Filed under:
  • XML Editor Reuse

    It is always good to see that someone else finds the code that you have written useful enough to be reused in another application. Here we take a look at where SharpDevelop's XML Editor has been reused. The XML Editor was originally added to SharpDevelop 1.0 back in April 2005.

    MonoDevelop

    Some time ago I ported SharpDevelop's XML Editor so it could be used from inside MonoDevelop. Currently there is an addin available for MonoDevelop 1.0. MonoDevelop 2.0 now ships with this XML Editor after Michael Hutchinson from Novell integrated it in March this year. I also believe that it is being used to help provide at least some part of the autocompletion for ASP.NET. It will be interesting to see how Michael builds on and improves the XML Editor code.

    Kaxaml

    Kaxaml is a lightweight XAML Editor written by Robby Ingebretsen. Kaxaml version 1.0 and 2.0 use a modified version of SharpDevelop's XML Editor. Robby has replaced the user interface part so the autocompletion popup window now uses WPF. He has also modified it so the autocompletion popup window behaves the same as Visual Studio's XML Editor. For example, SharpDevelop automatically inserts the equals sign and double quotes an attribute name is autocompleted whilst Visual Studio will autocomplete just the attribute name and then automatically insert the double quotes after the equals sign is typed in.

    Intellisense for Microsoft Expression Blend 2.5

    Stefan Dobrev has written an addin to provide XML autocompletion for the as yet unreleased Expression Blend 2.5. This addin uses the XML Editor code from Kaxaml to provide the autocompletion. Stefan has modified this code slightly to add support for the Expression Blend's code editor.

  • IronPython 2.0 Beta Integration

    Support for IronPython 2.0 Beta 4 is now available with SharpDevelop 3.

    Missing Features

    Some of the features have been disabled compared to the IronPython integration in SharpDevelop 2.

    • Forms designer
    • C# and VB.NET code conversion to Python

    Both of the above features involve converting code to and from a Code DOM. Support for the Code DOM is reduced in IronPython 2 so the above features have been temporarily disabled.

    Compiling

    IronPython 2.0 beta 4 re-introduced support for compiling python code to a .NET executable or dll and so SharpDevelop supports this. There is however one limitation. The working folder needs to be set to the folder containing the compiled dll or executable otherwise it will not be able to locate any local assembly references that are not in the GAC, for example IronPython.dll.

    IronPython Console

    There's now an IronPython console which can be used to type in IronPython expressions and have them evaluated interactively. It is currently missing code completion which will be implemented shortly. From the View menu select Tools and then Python Console.

    IronPython Console Window

    Posted Aug 20 2008, 09:27 PM by MattWard with no comments
    Filed under:
  • Attach to Process

    SharpDevelop 3 supports attaching the debugger to a running process.

    From the Debug menu select Attach to Process.

    Attach to Process menu item

    The Attach to Process dialog will show the managed processes by default. Select the process and then either double click or click the Attach button to attach to the process.

    Attach to Process dialog

    When you have finished debugging you can detach from the process by selecting Detach from the Debug menu.

    Detach menu item

    Posted Aug 20 2008, 09:16 PM by MattWard with no comments
    Filed under:
  • WiX 3.0 Integration

    SharpDevelop 3.0 now supports WiX 3.0.

    There are some changes to how things work compared to the WiX integration in SharpDevelop 2. The differences will be covered in the following sections.

    WiX Project Templates

    There are some new WiX project templates available. A basic empty project template and a template for each of the standard WiX UI library dialog sequences.

    Adding WiX Extensions

    WiX extensions are now displayed in the project browser instead of in the project options. They can be added by right clicking the WiX Extensions folder and selecting Add WiX Extension.

    .

    Project Options

    The Library and Linking tabs have been removed from the project options since the WiX extensions can now be added from the project browser.

    The Compiling tab has some new options as shown below.

    The WiX Variables field can be used to override the standard WiX UI library settings. In the screenshot above the standard licence agreement and dialog background bitmap are being replaced with new ones.

    The Suppress ICEs field is used to stop WiX from showing errors or warnings for particular Internal Consistency Evaluators (ICEs). After building your installer WiX now validates it against a standard set of rules which saves you from having to use another validation tool such as Orca.

    Localized string files are no longer specified in the Application's tab. Instead add the file to the project and change its Build action to Embedded Resource.

    Other Editors

    If you want integration with Visual Studio 2005 or 2008 then please check out Justin Rockwood's Votive.

    Rob Mensching has a list of WiX editors, including commerical ones, on his blog.

    It also looks like a future version of Visual Studio will ship with WiX.

     

    Posted Jan 02 2008, 11:28 PM by MattWard with no comments
    Filed under:
  • Porting an AddIn from SharpDevelop 2.2 to 3.0

    There have been some fairly large code changes on moving from SharpDevelop 2.2 to 3.0 and addins written for 2.2 are unlikely to work without some modification. We will look at the changes in the core parts of SharpDevelop and then look at one way to do the porting.

    Not all the differences are covered in the next section just those types and methods that are likely to have been used by an addin.

    ICSharpCode.Core

    TypeDescription
    ICSharpCode.Core.AddInReference
    bool RequirePreload New property that specifies that when a type from an addin is created any addins that are needed by this addin will be preloaded if this flag is set to true. This corresponds to the new requirePreload attribute in an .addin file.

    <Dependency addin="ICSharpCode.XmlEditor" requirePreload="true"/>

    ICSharpCode.Core.AddInTreeNode.TopologicalSortType is no longer public.
    ICSharpCode.Core.FileUtility
    bool IsValidPath(string path)New method that determines whether a full or relative path is valid. This replaces the IsValidFileName method.
    bool IsValidFileName(string fileName)Renamed to IsValidPath.
    static string NormalizePath(string fileName)New method that gets the normalized version of the filename. Slashes are replaced with backslashes, backreferences "." and ".." are 'evaluated'.
    ICSharpCode.Core.Properties
    void ReadProperties(XmlReader reader, string endElement)Method is no longer public but internal.

    NRefactory

    TypeDescription
    ICSharpCode.NRefactory.Ast.ArrayInitializerExpressionReplaced by the CollectionInitializerExpression type.
    ICSharpCode.NRefactory.Ast.BlockStatement
    BlockStatement NullThe Null property now returns a BlockStatement instead of a NullStatement type.
    ICSharpCode.NRefactory.Ast.FieldReferenceExpressionReplaced by the MemberReferenceExpression type.
    ICSharpCode.NRefactory.Ast.MemberReferenceExpressionReplaces the FieldReferenceExpression type.
    ICSharpCode.NRefactory.Ast.InvocationExpression
    List<TypeReference> TypeArgumentsThis property has been removed.
    ICSharpCode.NRefactory.Ast.NullArrayInitializerExpressionThis type has been removed.
    ICSharpCode.NRefactory.Ast.NullBlockStatementNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullConstructorInitializerNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullEventAddRegionNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullEventRaiseStatementNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullEventRemoveStatementNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullExpressionNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullPropertyGetRegionNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullPropertySetRegionNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.Ast.NullStatementNo longer a public type instead it is now internal.
    ICSharpCode.NRefactory.IAstVisitorVarious new methods on this interface to support new types.
    object VisitArrayInitializerExpression(...)Method removed along with the ArrayInitializerExpression type. Replaced by the VisitCollectionInitializerExpression method.
    object VisitFieldReferenceExpression(...)Method removed along with the ArrayInitializerExpression type. Replaced by the VisitMemberReferenceExpression method.
    ICSharpCode.NRefactory.Visitors.NodeTrackingAstVisitorAll TrackedVisit methods have been renamed and now include the name of the type being tracked (e.g. TrackedVisitAddHandlerStatement).

    ICSharpCode.SharpDevelop.Dom

    TypeDescription
    ICSharpCode.SharpDevelop.Dom.AbstractAmbience
    bool IncludeBodiesReplaced by the new IncludeBody property.
    bool IncludeBodyReplaces the IncludeBodies property.
    bool IncludeHTMLMarkupReplaced by the new IncludeHtmlMarkup property.
    bool IncludeHtmlMarkupReplaces the old IncludeHTMLMarkup property.
    bool UseFullyQualifiedMemberNamesReplaced by the new UseFullyQualifiedMemberTypeNames property.
    bool UseFullyQualifiedMemberTypeNamesReplaces the old UseFullyQualifiedMemberNames property.
    String Convert(ModifierEnum)Method removed.
    ICSharpCode.SharpDevelop.Dom.AbstractReturnType
    int TypeArgumentCountReplaces the old TypeParameterCount property.
    int TypeParameterCountReplaced by the new TypeArgumentCount property.
    ICSharpCode.SharpDevelop.Dom.AttributeArgumentType has been removed.
    ICSharpCode.SharpDevelop.Dom.ClassFinderThe constructors now take a ParseInformation type instead of a filename.
    ICSharpCode.SharpDevelop.Dom.ConversionFlags
    IncludeBodiesReplaced by IncludeBody.
    IncludeBodyReplaces IncludeBodies.
    IncludeHTMLMarkupReplaced by IncludeHtmlMarkup.
    IncludeHtmlMarkupReplaces IncludeHTMLMarkup.
    QualifiedNamesOnlyForReturnTypesEnum value removed.
    UseFullyQualifiedNamesReplaced by two new enums UseFullyQualifiedMemberNames and UseFullyQualifedTypeNames.
    UseFullyQualifiedMemberNamesReplaces the UseFullyQualifiedNames enum value.
    UseFullyQualifiedTypeNamesReplaces the UseFullyQualifiedNames enum value.
    ICSharpCode.SharpDevelop.Dom.CSharpExpressionFinder
    int LastExpressionStartPositionProperty has been removed.
    string FindExpressionInternal(string inText, int offset)Method has been removed.
    ctor(string fileName)Constructor now takes a ParseInformation type instead of the filename.
    ICSharpCode.SharpDevelop.Dom.CtrlSpaceResolveHelper
    ResolveResult GetResultFromDeclarationLine(IClass callingClass, IMethodOrProperty callingMember, int caretLine, int caretColumn, string expression)Obsolete method removed. Should use the GetResultFromDeclarationLine method that takes an ExpressionResult instead of a string.
    ICSharpCode.SharpDevelop.Dom.DefaultAttribute
    int CompareTo(IAttribute attribute)Method removed.
    string NameProperty has been removed. The attribute name can now be obtained from the Name property of the AttributeReturnType.
    ICSharpCode.SharpDevelop.Dom.DefaultCompilationUnit
    List<IClass> GetOuterClasses(int caretLine, int caretColumn)Method removed.
    ICSharpCode.SharpDevelop.DomRegion
    int CompareTo(DomRegion region)Method removed and replaced by the Equals method.
    ctor(Location start, Location end)Replaced by the static FromLocation method.
    ICSharpCode.SharpDevelop.ExpressionContext
    bool IsAttributeContextProperty has been removed. Instead compare the ExpressionContext against the ExpressionContext.Attribute.
    ExpressionContext GetAttribute(IProjectContent projectContent)Method removed. To indicate that an expression is an attribute use the ExpressionContext.Attribute type.
    ExpressionContext TypeDerivingFrom(IClass baseClass, bool isObjectCreation)Method now requires an IReturnType instead of an IClass type.
    ICSharpCode.SharpDevelop.ExpressionResult
    ctor(string expression, ExpressionContext context, object tag)Replaced by the new constructor that also takes a DomRegion.
    ctor(string expression, object tag)Replaced by the new constructor that also takes a DomRegion and ExpressionContext.
    ICSharpCode.SharpDevelop.GacAssemblyNameType removed. Code should use the DomAssemblyName instead.
    ICSharpCode.SharpDevelop.GacInterop
    GacAssemblyName FindBestMatchingAssemblyName(GacAssemblyName name)Method now uses DomAssemblyName types.
    GacAssemblyName FindBestMatchingAssemblyName(string name)Method now returns a DomAssemblyName type.
    ICSharpCode.SharpDevelop.GacInterop.AssemblyListEntryType has been removed. Code should now use a DomAssemblyName type.
    ICSharpCode.SharpDevelop.Dom.IAmbience
    String Convert(ModifierEnum)Method removed.
    ICSharpCode.SharpDevelop.Dom.IAttribute
    string NameProperty has been removed. The attribute name can now be obtained from the Name property of the AttributeReturnType.
    ICSharpCode.SharpDevelop.Dom.ICompilationUnit
    List<IClass> GetOuterClasses(int caretLine, int caretColumn) Method removed. Code should now use the IClass's DeclaringType property. For example:
        IClass type = callingClass.DeclaringType; 
    while (type != null) {
    ...
    type = type.DeclaringType;
    }
    ICSharpCode.SharpDevelop.Dom.IProjectContent
    IClass GetClass(string typeName)Method removed. Code should now use GetClass(string typeName, int typeParameterCount) and pass 0 for the typeParameterCount.
    ICSharpCode.SharpDevelop.Dom.IResolver
    ArrayList CtrlSpace(int caretLine, int caretColumn, string fileName, string fileContent, ExpressionContext context)Method changed to use a ParseInformation type instead of a filename.
    ResolveResult Resolve(ExpressionResult expressionResult, int line, int col, string fileName, string fileContent)Method changed to take a ParseInformation type instead of a filename and the line and column parameters have been removed.
    ICSharpCode.SharpDevelop.Dom.IReturnType
    int TypeArgumentCountReplaces the old TypeParameterCount property.
    int TypeParameterCountReplaced by the new TypeArgumentCount property.
    ICSharpCode.SharpDevelop.Dom.MemberLookupHelper
    IMethod FindOverload(IList<IMethod> methods, IReturnType[] typeParameters, IReturnType[] arguments)Method now has an extra out parameter called resultIsAcceptable. This parameter is true if the resulting method is an acceptable match, false if the resulting method is just a guess and will lead to a compile error.
    ICSharpCode.SharpDevelop.Dom.NRefactoryResolver
    bool Initialize(string fileName, int line, int column)Method now takes a ParseInformation type instead of a filename.
    IClass SearchClass(string name)Method now takes an extra Location parameter which is used to search for a class at a particular location in the file. Code that does not need to specify a location should use Location.Empty.
    IReturnType DynamicLookup(string identifier)Method now takes an extra Location parameter. Use Location.Empty if no location can be specified.
    IReturnType SearchMember(IReturnType type, string memberName)Method removed. Instead of using this method call GetMember and then use the IMember's ReturnType.
    IReturnType SearchType(string name)Method now takes an extra Location parameter. Use Location.Empty if no location can be specified.
    ResolveResult Resolve(ExpressionResult expressionResult, int line, int column, string fileName, string fileContent)Method now takes a ParseInformation type instead of the filename. The line and column parameters are no longer required.
    ArrayList CtrlSpace(int caretLine, int caretColumn, string fileName, string fileContent, ExpressionContext context)Method changed to use a ParseInformation type instead of a filename.
    ICSharpCode.SharpDevelop.Dom.ProjectContentRegistry
    IProjectContent GetExistingProjectContent(AssemblyName assembly)Obsolete method has been removed. Use the overloaded method that takes a DomAssemblyName type instead.
    IProjectContent GetExistingProjectContent(string itemInclude, string itemFileName)Method removed. Use the overloaded method that takes a single string parameter.
    ICSharpCode.SharpDevelop.Dom.ReflectionProjectContent
    AssemblyName[] ReferencedAssembliesProperty removed and replaced by the ReferencedAssemblyNames property which returns a list of DomAssemblyNames.
    IList<DomAssemblyName> ReferencedAssemblyNamesReplaces the old ReferencedAssemblies property.

    ICSharpCode.SharpDevelop.Widgets

    TypeDescription
    ICSharpCode.SharpDevelop.Widgets.SideBar.SideTab
    bool IsClipboardRingProperty removed.

    ICSharpCode.SharpDevelop

    TypeDescription
    ICSharpCode.SharpDevelop.ClassBrowserIconService
    int CombineIndexField removed and replaced with SolutionIndex.
    ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.CtrlSpaceCompletionDataProvider
    bool ForceNewExpressionProperty removed.
    ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.SharpDevelopTextEditorProperties
    bool CreateBackupCopyProperty removed.
    bool UseAntiAliasedFontProperty removed.
    ICSharpCode.SharpDevelop.FileService
    IWorkbenchWindow GetOpenFile(string fileName)Method now returns an IViewContent type.
    IWorkbenchWindow NewFile(string defaultName, string language, string content)Method now returns an IViewContent type and the language parameter has been removed.
    IWorkbenchWindow OpenFile(string fileName)Method now returns an IViewContent type.
    ICSharpCode.SharpDevelop.Gui.AbstractBaseViewContentClass has been removed.
    ICSharpCode.SharpDevelop.Gui.AbstractSecondaryViewContent
    void NotifyAfterSave(bool successful)Method removed.
    void NotifyBeforeSave()Method removed.
    void NotifyFileNameChanged()Method removed. Code should either use the TitleNameChanged event or the FileNameChanged event on the PrimaryFile.
    ICSharpCode.SharpDevelop.Gui.AbstractViewContent
    event DirtyChangedEvent renamed to IsDirtyChanged.
    string FileNameProperty renamed to PrimaryFileName.
    bool IsUntitledProperty removed. The property has been moved to the OpenedFile type which can be accessed via the view content's PrimaryFile property.
    void Load(string fileName)Method replaced by the Load method that takes an OpenedFile and a Stream.
    void Load(OpenedFile file, Stream stream)New method that is used to open a file in a view. The OpenedFile type contains information about the file, whilst the stream is the actual file data.
    event SavedEvent removed.
    event SavingEvent removed.
    string UntitledNameProperty removed. The untitled name is now stored as the filename.
    ICSharpCode.SharpDevelop.Gui.DefaultWorkbenchClass is now private and no longer publicly accessible.
    ICSharpCode.SharpDevelop.Gui.DriveObjectClass is now private and no longer publicly accessible.
    ICSharpCode.SharpDevelop.Gui.ExtTreeView
    void ApplyViewStateString(string state, TreeView tree)Method moved to the TreeViewHelper class.
    string GetViewStateString(TreeView tree)Method moved to the TreeViewHelper class.
    ICSharpCode.SharpDevelop.Gui.FileListClass is now private and no longer publicly accessible.
    ICSharpCode.SharpDevelop.Gui.IBaseViewContent Interface has been removed. Most of the interface has been moved to the IViewContent interface apart from the following methods which no longer exist.
    void Deselected()
    void Deselecting()
    void Selected()
    void SwitchedTo()
    ICSharpCode.SharpDevelop.Gui.ICanBeDirty
    event DirtyChangedEvent renamed to IsDirtyChanged.
    bool IsDirtyProperty setter is no longer defined in the interface. Only the getter is defined.
    ICSharpCode.SharpDevelop.Gui.IParseableContentInterface removed.
    ICSharpCode.SharpDevelop.Gui.ISecondaryViewContentInterface removed. All secondary view contents now implement the IViewContent interface.
    ICSharpCode.SharpDevelop.Gui.IViewContent
    bool IsDisposedNew property that indicates whether the view has been disposed.
    bool IsUntitledProperty removed. Code should check the PrimaryFile's Untitled property instead.
    event SavedEvent removed.
    bool SupportsSwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView)New method. Determines whether switching without a Save/Load is supported when switching from this view to another view.
    bool SupportsSwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView)New method. Determines whether switching without a Save/Load is supported when switching to this view from another view.
    IWorkbenchWindow WorkbenchWindowProperty moved from the now obsolete IBaseViewContent interface. Gives access to the workbench window associated with this view.
    List<ISecondaryViewContent> SecondaryViewContentsProperty now returns an ICollection instead of a list.
    event FileNameChangedEvent moved to the OpenedFile class which is accessible via the PrimaryFile property.
    event SavingEvent removed.
    OpenedFile PrimaryFileNew property that gives access to information about the primary file associated with this view.
    IList<OpenedFile> FilesNew property that returns a list of files that are associated with this view.
    event DisposedNew event raised when the view is disposed.
    event TabPageTextChangedNew event raised when the tab page text at the bottom of the window is changed.
    string FileNameThe filename associated with a view is now accessible from the PrimaryFileName property.
    string TabPageTextGets or sets the tab page text at the bottom of the window. This property was originally on the now obsolete IBaseViewContent interface.
    string UntitledNameProperty removed. The UntitledName is now the same as the PrimaryFileName.
    Control ControlGets or sets the control associated with this view. Originally part of the IBaseViewContent interface.
    void Load(string fileName)Replaced by the Load method that takes an OpenedFile and a Stream.
    void Load(OpenedFile file, Stream stream)New method that is used to open a file in a view. The OpenedFile type contains information about the file, whilst the stream is the actual file data..
    void Save()Replaced by the Save method that takes an OpenedFile and a Stream.
    void Save(string fileName)Replaced by the Save method that takes an OpenedFile and a Stream.
    void Save(OpenedFile file, Stream stream)New method that is used to save a file in a view.
    ICSharpCode.SharpDevelop.Gui.IViewContentMementoClass removed.
    ICSharpCode.SharpDevelop.Gui.IViewContentMementoCreatorClass removed.
    ICSharpCode.SharpDevelop.Gui.IWorkbenchWindow
    IBaseViewContent ActiveViewContentThe ActiveViewContent property is now an IViewContent.
    IViewContent ViewContentProperty removed. Use the ActiveViewContent property instead.
    ICSharpCode.SharpDevelop.IDisplayBinding
    bool CanCreateContentForFile(string fileName)Method now takes an OpenedFile type instead of a string.
    bool CanCreateContentForLanguage(string language)Method removed.
    ICSharpCode.SharpDevelop.ISecondaryDisplayBinding
    ISecondaryViewContent[] CreateSecondaryViewContent(IViewContent view)Method now returns an IViewContent array since the ISecondaryViewContent interface is obsolete.

    ICSharpCode.TextEditor

    TypeDescription
    ICSharpCode.TextEditor.Caret
    Point PositionThe Position property now uses the ICSharpCode.TextEditor.TextLocation type instead of the System.Drawing.Point type.
    Point ValidatePosition(Position pos)The method now returns and uses the ICSharpCode.TextEditor.TextLocation type instead of the System.Drawing.Point type.
    ICSharpCode.TextEditor.Document.Bookmark
    event LineNumberChangedEvent removed.
    ICSharpCode.TextEditor.Document.BookmarkManager
    event ChangedEvent removed.
    ICSharpCode.TextEditor.Document.DefaultFormattingStrategy
    int FormatLine(TextArea textArea, int line, int caretOffset, char charTyped)Method now no longer returns anything.
    ICSharpCode.TextEditor.Document.DefaultTextEditorProperties
    bool UseAntiAliasedFontProperty removed.
    ICSharpCode.TextEditor.Document.IDocument
    LineSegment GetLineSegment(int line)The default implementation in the DefaultLineSegment class no longer allows the line number to be equal to the number of items in the IDocument's LineSegment collection.
    Point OffsetToPosition(int offset)Method now returns a TextLocation type.
    int PositionToOffset(Point point)Method now takes a TextLocation type.
    ICSharpCode.TextEditor.Document.IFormattingStrategy
    int FormatLine(TextArea textArea, int line, int caretOffset, char charTyped)Method now no longer returns anything.
    ICSharpCode.TextEditor.Document.ILineManagerInterface has been removed.
    ICSharpCode.TextEditor.Document.ISelection
    bool ContainsPosition(Point point)Method now uses a TextLocation type.
    Point EndPositionProperty now uses a TextLocation type.
    Point StartPositionProperty now uses a TextLocation type.
    ICSharpCode.TextEditor.Document.ITextEditorProperties
    bool CreateBackupCopyProperty removed.
    bool UseAntiAliasedFontProperty removed.
    ICSharpCode.TextEditor.Document.LineLengthEventArgsReplaced by the LineLengthChangedEventArgs type.
    ICSharpCode.TextEditor.Gui.InsightWindow.IInsightDataProvider
    char CharTypedProperty removed.
    ICSharpCode.TextEditor.TextEditorControlBase
    event ChangedEvent renamed to TextChanged.
    bool CreateBackupCopyProperty removed.
    bool IsUpdatingProperty removed.
    bool UseAntiAliasedFontProperty removed.
    ICSharpCode.TextEditor.ToolTipRequestEventArgs
    Point LogicalPositionProperty now uses a TextLocation type.
    ICSharpCode.TextEditor.Undo.UndoStack
    void CombineLast(int actionCount)Method removed. Similar functionality can be obtained by using the StartUndoGroup and EndUndoGroup method.
    void UndoLast(int actionCount)Method removed. Similar functionality can be obtained by using the StartUndoGroup and EndUndoGroup method.

    How to Port

    This section looks at one way to port an addin. This is the way that the IronPython addin was ported from SharpDevelop 2.2 to SharpDevelop 3.

    Download the source code for SharpDevelop 3.0 and 2.2.

    Copy the source code of your addin to a folder inside SharpDevelop 3.0.

    Run three copies of SharpDevelop, one to view SharpDevelop 2.2's source, one to view SharpDevelop 3.0's source, one with your addin. In the other copies of SharpDevelop you should open SharpDevelop.sln for 2.2 and 3.0. This will allow you to quickly search for SharpDevelop classes when you find your addin code will not compile.

    Compile your code and fix the code or comment it out as you try to work out what is wrong.

    You do not have to convert the project to use .NET 3.5. SharpDevelop itself uses it, but some of the addins do not. With the IronPython addin I needed to change it to use .NET 3.5 since there were some assembly conflicts between what SharpDevelop was using and what the addin was using. Obviously changing to use .NET 3.5 will allow you to use any new features of this framework.

    .NET Framework Differences

    One change in .NET 3.5 caused me a few problems when porting the IronPython addin. This was a change in MSBuild.

    After finally getting the IronPython addin to compile with the modified SharpDevelop 3 assemblies there was an assembly conflict for MSBuild. The ICSharpCode.SharpDevelop assembly now uses MSBuild 3.5 assemblies whilst the IronPython build task project and test project were using MSBuild 2.0. This was fixed by converting these two projects so they use the .NET Framework 3.5. This can be done by opening the project's properties, selecting the Compiling tab, then clicking the Convert Project to C# 3.0 button. In the dialog that opens we select the "Change target framework to .NET 3.5" and click the OK button. After making this change the addin compiled and worked however all the unit tests for the IronPython build tasks were failing with the error:

    System.ArrayTypeMismatchException : Attempted to access an element as a type incompatible with the array.

    The test code that was failing:

    PythonCompilerTask compiler = new PythonCompilerTask(); 
    TaskItem sourceTaskItem = new TaskItem("test.py");
    compiler.Sources = new ITaskItem[] {sourceTaskItem};

    The last line was failing when the unit test was run. The TaskItem type implements the ITaskItem interface so the code should have worked. The reason for this error is that both the build task and build task tests project were referencing the MSBuild libraries just using the reference name.

        <Reference Include="Microsoft.Build.Framework" /> 
    <Reference Include="Microsoft.Build.Tasks" />
    <Reference Include="Microsoft.Build.Utilities" />

    Running MSBuild from the command line the actual assemblies being used were a mix of 3.5 and 2.0 versions.

    "Python.Build.Tasks.csproj": 
    /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\Microsoft.Build.Framework.dll"
    /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Tasks.dll
    /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Utilities.dll

    Printing out the code base of the TaskItem and ITaskItem types whilst running the unit tests showed that the TaskItem was being taken from Microsoft.Build.Utilities assembly version 2.0 and the ITaskItem was being taken the Microsoft.Build.Framework 3.5 assembly. The TaskItem we actually needed was in the new Microsoft.Build.Utilities assembly version 3.5. Changing the project references so they used this new 3.5 assembly fixed the problem.

    Posted Dez 09 2007, 05:28 PM by MattWard with no comments
    Filed under:
  • IronPython AddIn Internals

    This is a tutorial about how to create a language binding for SharpDevelop using the IronPython addin as an example. As well as covering how to create a language binding it will also look at how the addin used IronPython. The source code for the IronPython addin is available at the end of this tutorial.

    The tutorial will cover the following.

    Before You Begin

    The starting point of this tutorial is a basic addin project. Daniel Grunwald created a video tutorial on how to create an addin for SharpDevelop, a transcript of this video is available on the wiki. The information presented in those two links will not be repeated here beyond the following summary.

    • Two copies of SharpDevelop. One for developing and one for testing the addin.
    • A new addin project called PythonBinding. Create this project using the SharpDevelop addin template, remove the TestPad and MyUserControl classes since you will not be using them, and modify the .addin file as shown below.
    • In the project options change the output path for the addin so it builds into a subfolder inside SharpDevelop's AddIn Folder. This will be the copy of SharpDevelop that you are using to test with.
    • Add references to ICSharpCode.SharpDevelop and ICSharpCode.Core. Set Local Copy to false for both of these references.
    <AddIn name="Python Binding" 
    author=""
    description="Backend binding for IronPython">

    <Manifest>
    <Identity name="ICSharpCode.PythonBinding"/>
    </Manifest>

    <Runtime>
    <Import assembly=":ICSharpCode.SharpDevelop"/>
    <Import assembly="PythonBinding.dll"/>
    </Runtime>
    </AddIn>

    Syntax Highlighting

    For syntax highlighting we need a highlighting definition file (.xshd). A good starting point for writing one of these is to look at the existing files provided with SharpDevelop and create your own based on these. One thing that is worth noting is that if you want to be able to comment and uncomment a block of code using SharpDevelop's comment region toolbar button you will need to define a LineComment property in your .xshd file as shown below.

    <Properties> 
    <Property name="LineComment" value="#"/>
    </Properties>

    This .xshd file needs to be added to the PythonBinding project and embedded as a resource in the addin. First create a new folder in the project called Resources and add the .xshd file to the project inside this folder. You can use the .xshd that is provided with the IronPython addin source code or create your own. In the Projects window, right click the added .xshd file, select Properties and change the Build action to EmbeddedResource.

    To tell SharpDevelop about this highlighting definition file we need to add a SyntaxMode element to the PythonBinding.addin file.

    <Path name="/SharpDevelop/ViewContent/DefaultTextEditor/SyntaxModes"> 
    <SyntaxMode id="Python.SyntaxMode"
    extensions=".py"
    name="Python"
    resource="ICSharpCode.PythonBinding.Resources.Python.xshd"/>
    </Path>

    Depending on what the root namespace of your addin project is you may need to modify the value of the resource attribute in the SyntaxMode element. In the example shown above the root namespace is ICSharpCode.PythonBinding.

    File Filters

    When the open file dialog is displayed we want to provide a filter to show only Python files (.py). This is done by adding the following FileFilter element to the PythonBinding.addin file.

     <!-- Add the "Python" entry to the Open File Dialog --> 
    <Path name="/SharpDevelop/Workbench/FileFilter">
    <FileFilter id="Python"
    insertbefore="Resources"
    insertafter="Icons"
    name="Python Files (*.py)"
    extensions="*.py"/>
    </Path>

    The insertbefore and insertafter attributes determine where in the list of file filters the new file filter will be shown.

    Similary when we open a project we want to provide a filter to show only Python project files (.pyproj). As before we add a FileFilter element to the .addin file.

     <!-- Add the "Python" entry to the Open Project Dialog --> 
    <Path name = "/SharpDevelop/Workbench/Combine/FileFilter">
    <FileFilter id="PythonProject"
    insertbefore="AllFiles"
    name="Python Project Files (*.pyproj)"
    class="ICSharpCode.SharpDevelop.Project.LoadProject"
    extensions="*.pyproj"/>
    </Path>

    Project and File Templates

    Create a new folder in your addin project called Templates. Inside here you put file templates (.xft) and project templates (.xpt). For each file you add make sure the Copy to output directory property is set to Always. To change this select the file in the Projects window, right click, select Properties and change the drop down value. When you build your addin the Templates folder and all the templates should be copied to a subdirectory where the addin is built. In order to tell SharpDevelop about these new templates we add the following to the PythonBinding.addin file.

     <!-- File templates --> 
    <Path name="/SharpDevelop/BackendBindings/Templates">
    <Directory id="Python" path="./Templates" />
    </Path>

    Compiling a Project

    SharpDevelop uses MSBuild to compile projects so in order to support compiling a Python project we need to create an MSBuild task and associated .targets file. With the IronPython addin there is a separate Python.Build.Tasks project contains a PythonCompilerTask class and a SharpDevelop.Build.Python.targets file. The Python.Build.Tasks project gets built into the same folder as the addin. The .targets file has its Copy to output directory property set to Always.

    The Python.Build.Tasks project also contains an IPythonCompiler interface and a PythonCompiler class which are essentially empty. These are used to unit test the PythonCompilerTask making sure it correctly sets properties on the compiler.

    The PythonCompilerTask's job is to map from MSBuild types to types the IronPython's PythonCompiler can handle and compile the Python code to an assembly. Compiling with IronPython's PythonCompiler is straightforward, as shown below.

       using (compiler) { 
    // Set what sort of assembly we are generating
    // (e.g. WinExe, Exe or Dll)
    compiler.TargetKind = PEFileKinds.WindowApplication;

    compiler.SourceFiles = new string[] { "Program.py";
    compiler.MainFile = "Program.py";
    compiler.OutputAssembly = "test.exe";

    // Compile the code.
    compiler.Compile();
    }

    The most important part is telling the compiler which file contains the main entry point into the assembly.

    In order to get SharpDevelop to use this new build task we need to do two things, tell SharpDevelop how to find the .targets file and implement a language binding class. The language binding class is used to create the Python project file (.pyproj) which is an MSBuild file. The created .pyproj file references the .targets file via a PythonBinPath property as shown below.

      <Import Project="$(PythonBinPath)\SharpDevelop.Build.Python.targets" /> 

    This PythonBinPath needs to be defined so SharpDevelop can set it before building the project with MSBuild. Otherwise the build will fail. To do this we add a String element to the PythonBinding.addin file.

     <!--  
    Register path to SharpDevelop.Build.Python.targets for the MSBuild engine.
    SharpDevelop.Build.Python.targets is in the PythonBinding AddIn directory
    -->
    <Path name="/SharpDevelop/MSBuildEngine/AdditionalProperties">
    <String id="PythonBinPath" text="${AddInPath:ICSharpCode.PythonBinding}"/>
    </Path>

    Here the PythonBinPath is set so it points to the addin's build folder which is also where the .targets file is stored.

    The language binding is defined by adding a LanguageBinding element to the PythonBinding.addin file.

     <!-- Register Python MSBuild project (.pyproj) --> 
    <Path name="/SharpDevelop/Workbench/LanguageBindings">
    <LanguageBinding id="Python"
    guid="{FD48973F-F585-4F70-812B-4D0503B36CE9}"
    supportedextensions=".py"
    projectfileextension=".pyproj"
    class="ICSharpCode.PythonBinding.PythonLanguageBinding" />
    </Path>

    This defines the Project Guid that will be inserted into the Python MSBuild file (.pyproj), it defines the project file extension (.pyproj) and the class that implements the ILanguageBinding interface (PythonLanguageBinding).

    When you open a Python project SharpDevelop looks through the list of language bindings for one that supports the .pyproj file extension. It finds that the PythonBinding.addin has defined such a language binding and SharpDevelop returns an instance of the PythonLanguageBinding class. This class is responsible for loading an existing Python project and creating a new one. The two main methods of this class are LoadProject and CreateProject. The PythonLanguageBinding implementation of these are shown below.

      public IProject LoadProject(IMSBuildEngineProvider engineProvider, string fileName, string projectName) 
    {
    return new PythonProject(engineProvider, fileName, projectName);
    }

    public IProject CreateProject(ProjectCreateInformation info)
    {
    return new PythonProject(info);
    }

    Both of these methods create a new instance of the PythonProject class which represents the MSBuild project file (.pyproj). Most of the implementation for the PythonProject class is provided for you in by its base class which is SharpDevelop's CompilableProject class. All the PythonProject needs to do is add the import statement for the SharpDevelop.Build.Python.targets file when a new project is created, and when a new file is added to the project its type is set to Compile if its extension is .py. Setting the type to Compile means that a new Python file added to the project will be added inside a Compile element as shown below.

        <Compile Include="Class1.py" /> 

    Only files defined inside a Compile element will be compiled by the Python build task.

    To do all this two methods, GetDefaultItemType and Create, need to be overridden in the PythonProject class as shown below.

      public const string DefaultTargetsFile =
    @"$(PythonBinPath)\SharpDevelop.Build.Python.targets";

    /// <summary>
    /// Returns ItemType.Compile if the filename has a
    /// python extension (.py).
    /// </summary>
    public override ItemType GetDefaultItemType(string fileName)
    {
    if (fileName != null) {
    string extension = Path.GetExtension(fileName);
    if (extension.ToLowerInvariant() == ".py") {
    return ItemType.Compile;
    }
    }
    return base.GetDefaultItemType(fileName);
    }

    protected override void Create(ProjectCreateInformation information)
    {
    base.Create(information);
    AddImport(DefaultTargetsFile, null);
    }

    You will also need to add a reference to ICSharpCode.SharpDevelop.Dom in order to compile the PythonProject class.

    Project Options

    Just adding a language binding and project class there are no project options defined. Some of the existing project options (e.g. BuildEvents, DebugOptions) can be re-used but the Python project still needs custom project options for Application Settings and its Compiling options.

    A custom project option consists of a class derived from the AbstractBuildOptions class and an XML form (.xfrm). The XML form needs to be embedded as a resource and can be designed with SharpDevelop's forms designer. A slightly simplified version of the CompilingOptionsPanel class is shown below.

     public class CompilingOptionsPanel : AbstractBuildOptions 
    {
    public override void LoadPanelContents()
    {
    string resourceName = "ICSharpCode.PythonBinding.Resources.CompilingOptionsPanel.xfrm";
    SetupFromXmlStream(typeof(CompilingOptionsPanel).Assembly.GetManifestResourceStream(resourceName));
    InitializeHelper();

    ConfigurationGuiBinding b = helper.BindString("outputPathTextBox", "OutputPath", TextBoxEditMode.EditRawProperty);
    b.ConnectBrowseFolder("outputPathBrowseButton", "outputPathTextBox", "Choose Folder", TextBoxEditMode.EditRawProperty);

    helper.AddConfigurationSelector(this);
    }
    }

    This class takes the CompilingOptionsPanel XML form, embedded as a resource, reads it and adds any controls defined in it to the options panel control. It then uses a helper class to bind an MSBuild property called OutputPath to a text box. After that it connects a button on the XML form to a browse folder dialog. Finally it adds a configuration selector which means the options dialog will have the Debug/Release drop down at the top allowing the user to configure the MSBuild property in the Release or Debug builds of the project.

    The PythonBinding.addin file then needs to be changed so SharpDevelop is made aware of these new options dialogs for a Python project.

     <!-- Project options panels --> 
    <Path path="/SharpDevelop/BackendBindings/ProjectOptions/Python">
    <DialogPanel id="Application"
    label="Application"
    class="ICSharpCode.PythonBinding.ApplicationSettingsPanel"/>
    <DialogPanel id="BuildEvents"
    label="Build Events"
    class="ICSharpCode.SharpDevelop.Gui.OptionPanels.BuildEvents"/>
    <DialogPanel id="CompilingOptions"
    label="Compiling"
    class="ICSharpCode.PythonBinding.CompilingOptionsPanel"/>
    <DialogPanel id="DebugOptions"
    label="Debug"
    class="ICSharpCode.SharpDevelop.Gui.OptionPanels.DebugOptions"/>
    </Path>

    Note that above we are re-using some of the existing options panels as well as including our custom ones.

    Code Folding

    To support code folding we need to parse the Python code and determine where the folds need to be placed. To parse the code we use the parser provided by IronPython as shown below.

    PythonCompilerSink sink = new PythonCompilerSink(); 
    CompilerContext context = new CompilerContext(fileName, sink);
    Parser parser = Parser.FromString(null, context, fileContent);
    Statement statement = parser.ParseFileInput();

    Here we are using a custom compiler sink (PythonCompilerSink) which is only used to suppress some of the exceptions thrown by IronPython's parser. If no sink is passed into the CompilerContext then IronPython's SimpleParserSink which throws an exception if the parser comes across anywhere errors. If an error occurs whilst parsing a class the parser will not return any class information. Suppressing the exception will mean at least some class information is returned from the parser.

    Now that we have a parser we need to use it from inside SharpDevelop. This is done by implementing the IParser interface. In the IronPython AddIn this interface is implemented in the PythonParser class. The one IParser method that needs to be implemented for folding is the Parse method.

    ICompilationUnit Parse(IProjectContent projectContent, string fileName, string fileContent);

    This method takes the Python source code, parses it and returns a code compile unit, which is a container for the code document object model that SharpDevelop understands.

    The IronPython parser returns an AST statement which needs to be converted to SharpDevelop's code document object model. This is then done by walking the AST using a custom AST walker (PythonAstWalker).

    PythonAstWalker walker = new PythonAstWalker(projectContent); 
    walker.Walk(statement);
    walker.CompilationUnit.FileName = fileName;
    return walker.CompilationUnit;

    The PythonAstWalker walks the AST looking for classes and methods and their positions in the source code which it records. The positions are recorded so SharpDevelop can create folds at the correct locations in the code. Part of the PythonAstWalker class is shown below. Here a class and its location is recorded.

     public class PythonAstWalker : AstWalker 
    {
    DefaultCompilationUnit compilationUnit;
    DefaultClass currentClass;

    public PythonAstWalker(IProjectContent projectContent)
    {
    compilationUnit = new DefaultCompilationUnit(projectContent);
    }

    /// <summary>
    /// Walks the python statement returned from the parser.
    /// </summary>
    public void Walk(Statement statement)
    {
    statement.Walk(this);
    }

    /// <summary>
    /// Walks a class definition.
    /// </summary>
    public override bool Walk(ClassDefinition node)
    {
    DefaultClass c = new DefaultClass(compilationUnit, node.Name.ToString());
    c.Region = new DomRegion(node.Start.Line,
    node.Start.Column + 1,
    node.Start.Line,
    node.Body.Start.Column + 1);
    Location start = node.Body.Start;
    Location end = node.Body.End;
    c.BodyRegion = new DomRegion(start.Line, start.Column + 2, end.Line, end.Column);

    // Save the class.
    compilationUnit.Classes.Add(c);

    // Walk through all the class items.
    currentClass = c;
    node.Body.Walk(this);
    currentClass = null;

    return false;
    }

    SharpDevelop needs to be told about the PythonParser so we add a new Parser element to the PythonBinding.addin.

     <Path name="/Workspace/Parser"> 
    <Parser id="Python"
    supportedextensions=".py"
    projectfileextension=".pyproj"
    class="ICSharpCode.PythonBinding.PythonParser"/>
    </Path>

    We have made our first use of the IronPython assembly so we need to add a reference to it in our project.

    Class View

    Implementing the parser has the side effect that classes and methods now appear in the Class View. The Class View can also display properties, fields, events, base types and method parameters. Information about these items need to be extracted from the AST and added to the code document object model. In the IronPython addin, the current implementation of the PythonAstWalker will add the base type of a class and method parameters to the code dom. Base types are needed for the forms designer when checking if a form can be designed so we will take a quick look at how to do this before moving on to the forms designer itself.

    First we need to modify the PythonAstWalker class. In the Walk(ClassDefinition node) method, shown in the previous section, we add a call to an AddBaseTypes method just before the compilationUnit.Classes.Add.

       AddBaseTypes(c, node.Bases); 

    The AddBaseTypes method looks at each expression in the Bases collection and adds the name of the type to the class.

      void AddBaseTypes(IClass c, IList<Expression> baseTypes) 
    {
    foreach (Expression expression in baseTypes) {
    NameExpression nameExpression = expression as NameExpression;
    FieldExpression fieldExpression = expression as FieldExpression;
    if (nameExpression != null) {
    AddBaseType(c, nameExpression.Name.ToString());
    } else if (fieldExpression != null) {
    AddBaseType(c, fieldExpression.Name.ToString());
    }
    }
    }

    /// <summary>
    /// Adds the named base type to the class.
    /// </summary>
    void AddBaseType(IClass c, string name)
    {
    c.BaseTypes.Add(new SearchClassReturnType(c.ProjectContent, c, 0, 0, name, 0));
    }

    If the base class is fully qualified (i.e. System.Windows.Forms.Form) in the source code then the IronPython parser will return a FieldExpression otherwise it will return a NameExpression.

    Creating a Forms Designer

    Writing a forms designer is fairly complicated mainly because of the number of classes that you need to implement before you can see the designer working in SharpDevelop. The classes needed and their relationships are shown below.

    Forms designer classes

    The secondary display binding is responsible for creating the designer window when a form is open in the text editor. It also creates the forms designer, designer loader provider and designer generator.

    The designer loader provider's only job is to create the designer loader. The designer loader is responsible for reading the form's source code, generating a code DOM from it so the forms designer can display the form.

    The designer generator is responsible for updating the form's InitializeComponent method and generating event handlers.

    Now we will look at creating the Python forms designer. The approach we will take is to do the bare minimum to get things working, using dummy values where required and then building on this base to get everything properly. The first step is to get the loading working.

    First we tell SharpDevelop about our new secondary display binding by adding the following to the PythonBinding.addin file.

     <!-- Python display binding --> 
    <Path name="/SharpDevelop/Workbench/DisplayBindings">
    <DisplayBinding id="PythonDisplayBinding"
    type="Secondary"
    fileNamePattern="\.py$"
    languagePattern="^Python$"
    class="ICSharpCode.PythonBinding.PythonFormsDesignerDisplayBinding" />
    </Path>

    You will need to add a reference to the FormsDesigner.dll and ICSharpCode.TextEditor.dll in order to compile the project. Both of these references should have Local copy set to false.

    You should also indicate to SharpDevelop that the IronPython addin depends on the FormsDesigner addin so if that is missing or disabled the IronPython addin will not be loaded. This is done by adding a Dependency element to the PythonBinding.addin file.

     <Manifest> 
    <Identity name="ICSharpCode.PythonBinding"/>
    <Dependency addin="ICSharpCode.FormsDesigner"/>
    </Manifest>

    You will also need to add the FormsDesigner.dll to the Runtime section of the PythonBinding.addin file.

     <Runtime> 
    <Import assembly=":ICSharpCode.SharpDevelop"/>
    <Import assembly="$ICSharpCode.FormsDesigner/FormsDesigner.dll"/>
    <Import assembly="PythonBinding.dll"/>
    </Runtime>

    The dollar sign indicates to SharpDevelop that it should look in the folder containing the ICSharpCode.FormsDesigner addin.

    Now we need to create four classes: PythonFormsDesignerBinding, PythonDesignerLoaderProvider, PythonDesignerLoader and the PythonDesignerGenerator.

    Our first pass at the PythonFormsDesignerBinding is shown below.

     public class PythonFormsDesignerDisplayBinding : ISecondaryDisplayBinding 
    {
    public PythonFormsDesignerDisplayBinding()
    {
    }

    public bool ReattachWhenParserServiceIsReady {
    get { return false; }
    }

    public bool CanAttachTo(IViewContent content)
    {
    return true;
    }

    public ISecondaryViewContent[] CreateSecondaryViewContent(IViewContent viewContent)
    {
    IDesignerLoaderProvider loader = new PythonDesignerLoaderProvider();
    IDesignerGenerator generator = new PythonDesignerGenerator();
    return new ISecondaryViewContent[] { new FormsDesignerViewContent(viewContent, loader, generator) };
    }

    There are several things wrong with the above class but it is enough to get us started. The CanAttachTo method should only attach to a Python file that contains a form or a user control. At the moment it attaches to any open file. The ReattachWhenParserServiceIsReady should return true. This is because when a large project is opened the parser may take a few seconds to finish parsing and so we may not know if the currently open file actually contains a form or user control. Returning false means we may not be able to get to the design view for a file without closing it and re-opening it. For the current implementation this is not a problem. The CreateSecondaryViewContent method is responsible for creating the forms designer, loader provider and generator. It should also make sure that it has not already created a secondary display binding for the current view but it in the above code this is not done. This is more important when ReattachWhenParserServiceIsReady returns true since we may end up with two designer windows shown. We will come back to these problems later.

    The designer loader provider's only job is to create a designer loader.

     public class PythonDesignerLoaderProvider : IDesignerLoaderProvider 
    {
    public DesignerLoader CreateLoader(IDesignerGenerator generator)
    {
    return new PythonDesignerLoader();
    }
    }

    Our first pass at a designer loader is shown below.

     public class PythonDesignerLoader : CodeDomDesignerLoader 
    {
    PythonProvider codeDomProvider = new PythonProvider();

    public PythonDesignerLoader()
    {
    }

    protected override CodeDomProvider CodeDomProvider {
    get { return codeDomProvider; }
    }

    protected override ITypeResolutionService TypeResolutionService {
    get { return null; }
    }

    protected override CodeCompileUnit Parse()
    {
    return new CodeCompileUnit();
    }

    protected override void Write(CodeCompileUnit unit)
    {
    }
    }
    }

    There are some things wrong with this. The Parse method should parse the source code and generate a code DOM that can be loaded by the forms designer. The Write method should generate the Python code and update the form's source code. Generally this is done by calling the designer generator's MergeFormChanges method. Again we will address these problems later on.

    The PythonDesignerLoader is derived from Microsoft's CodeDomDesignerLoader so you will need to add a reference to System.Design.

    The designer generator implements the IDesignerGenerator interface. For now we implement empty methods.

     public class PythonDesignerGenerator : IDesignerGenerator 
    {
    PythonProvider pythonProvider = new PythonProvider();
    FormsDesignerViewContent viewContent;

    public PythonDesignerGenerator()
    {
    }

    public CodeDomProvider CodeDomProvider {
    get { return pythonProvider; }
    }

    public void Attach(FormsDesignerViewContent viewContent)
    {
    }

    public void Detach()
    {
    }

    public void MergeFormChanges(CodeCompileUnit unit)
    {
    }

    public bool InsertComponentEvent(IComponent component, EventDescriptor edesc, string eventMethodName, string body, out string file, out int position)
    {
    position = 0;
    file = String.Empty;
    return true;
    }

    public ICollection GetCompatibleMethods(EventDescriptor edesc)
    {
    return new ArrayList();
    }

    [Obsolete("This method is not used by the forms designer.")]
    public ICollection GetCompatibleMethods(EventInfo edesc)
    {
    return new ArrayList();
    }
    }

    The MergeFormChanges method should update the form's source code with any changes made in the designer. This method is called when the forms designer is open and the form is saved or when the source code is switched to.

    The InsertComponentEvent method is used to create an empty event handler in the form's source code.

    The GetCompatibleMethods method is used to return methods that are compatible with an event. These methods will then be shown in the Properties window in the drop down list for an event.

    If the IronPython addin is now built you can switch to the forms designer and back again but you will get an error saying that nothing can be designed. To get the forms designer to work we need to return a CodeCompileUnit that contains a form. As a quick test we can use the IronPython's PythonProvider to create a CodeCompileUnit from a hard coded Python source code string.

      protected override CodeCompileUnit Parse() 
    {
    string source = "class MainForm(System.Windows.Forms.Form):\r\n" +
    " def __init__(self):\r\n" +
    " self.InitializeComponent()\r\n" +
    "\r\n" +
    " def InitializeComponent(self):\r\n" +
    " pass\r\n";

    PythonProvider provider = new PythonProvider();
    return provider.Parse(new StringReader(source));
    }

    The above code should be added to the designer loader. Note that we are using the fully qualified name for the base class (System.Windows.Forms.Form) otherwise the designer will not be able to create a form instance to display in the designer. This is something that we will need to fix later.

    Now we will get the generator working. The generator's MergeFormChanges method needs to be called from the loader's Write method. The loader will also need to be told about the generator so we need to change the loader provider as shown below.

     public class PythonDesignerLoaderProvider : IDesignerLoaderProvider 
    {
    public DesignerLoader CreateLoader(IDesignerGenerator generator)
    {
    return new PythonDesignerLoader(generator);
    }
    }

    The loader can now call the generator's MergeFormChanges method.

      IDesignerGenerator generator; 

    public PythonDesignerLoader(IDesignerGenerator generator)
    {
    this.generator = generator;
    }

    protected override void Write(CodeCompileUnit unit)
    {
    generator.MergeFormChanges(unit);
    }

    The generator's MergeFormChanges method needs to get access to the text editor, find the InitializeComponent method, generate the new python source code from the CodeCompileUnit and then update the source code in the text editor.

      FormsDesignerViewContent viewContent; 

    public void Attach(FormsDesignerViewContent viewContent)
    {
    this.viewContent = viewContent;
    }

    public void Detach()
    {
    this.viewContent = null;
    }

    public void MergeFormChanges(CodeCompileUnit unit)
    {
    GeneratedInitializeComponentMethod generatedInitalizeComponentMethod = GeneratedInitializeComponentMethod.GetGeneratedInitializeComponentMethod(unit);
    if (generatedInitalizeComponentMethod == null) {
    throw new InvalidOperationException("InitializeComponent not found in generated code.");
    }

    TextEditorControl textEditor = viewContent.TextEditorControl;
    ParseInformation parseInfo = ParserService.ParseFile(textEditor.FileName, textEditor.Text);
    generatedInitalizeComponentMethod.Merge(textEditor.Document, parseInfo.BestCompilationUnit);
    }

    The MergeFormChanges method locates the InitializeComponent method in the generated code dom and then updates the source code.

      public void Merge(IDocument document, ICompilationUnit compilationUnit) 
    {
    // Get the document's initialize components method.
    IMethod documentInitializeComponentsMethod = GetInitializeComponents(compilationUnit);

    // Generate source code from the code DOM.
    string generatedCode = GenerateCode();
    Console.WriteLine("GeneratedCode: " + generatedCode);

    // Parse the generated source code so we can
    // find the InitializeComponent method. We can
    // only generate code for the entire form.
    IMethod generatedCodeInitializeComponentMethod = GetInitializeComponentFromGeneratedCode(generatedCode);
    string generatedInitializeComponentsMethodBody = GetMethodBody(generatedCodeInitializeComponentMethod, generatedCode);

    // Merge the code.
    DomRegion methodRegion = GetBodyRegionInDocument(documentInitializeComponentsMethod);
    int startOffset = document.PositionToOffset(new Point(methodRegion.BeginColumn - 1, methodRegion.BeginLine - 1));
    int endOffset = document.PositionToOffset(new Point(methodRegion.EndColumn - 1, methodRegion.EndLine - 1));
    document.Replace(startOffset, endOffset - startOffset, generatedInitializeComponentsMethodBody);
    }

    The GeneratedInitializeComponentMethod's Merge method is shown above, for the other methods take a look at the actual class since there is too much code to go through all of it here. The Merge method locates the InitializeComponent method in the text editor, generates the new Python code, locates the InitializeComponent method in the generated code and then updates the source code. The Python code is generated using the IronPython's PythonProvider's GenerateCodeFromType method.

    The Python forms designer will now generate the correct code and update the text editor so let us return to the loader and get it working with the actual source code. First we need to pass the source code document to the designer loader provider so we update the PythonFormsDesignerDisplayBinding class.

      public ISecondaryViewContent[] CreateSecondaryViewContent(IViewContent viewContent) 
    {
    TextEditorControl textEditor = ((ITextEditorControlProvider)viewContent).TextEditorControl;
    IDesignerLoaderProvider loader = new PythonDesignerLoaderProvider(textEditor.Document);
    IDesignerGenerator generator = new PythonDesignerGenerator();
    return new ISecondaryViewContent[] { new FormsDesignerViewContent(viewContent, loader, generator) };
    }

    The designer loader provider just passes the document onto the designer loader as shown below.

     public class PythonDesignerLoaderProvider : IDesignerLoaderProvider 
    {
    IDocument document;

    public PythonDesignerLoaderProvider(IDocument document)
    {
    this.document = document;
    }

    public DesignerLoader CreateLoader(IDesignerGenerator generator)
    {
    return new PythonDesignerLoader(document, generator);
    }
    }

    Now we can get the source code from the document in the designer loader.

     public class PythonDesignerLoader : CodeDomDesignerLoader 
    {
    PythonProvider codeDomProvider = new PythonProvider();
    IDesignerGenerator generator;
    IDocument document;

    public PythonDesignerLoader(IDocument document, IDesignerGenerator generator)
    {
    this.document = document;
    this.generator = generator;
    }

    protected override CodeCompileUnit Parse()
    {
    PythonProvider provider = new PythonProvider();
    return provider.Parse(new StringReader(document.TextContent));
    }

    The designer loader will work with simple forms as long as the base class is fully qualified so let's fix that.

      protected override CodeCompileUnit Parse() 
    {
    PythonProvider provider = new PythonProvider();
    CodeCompileUnit unit = provider.Parse(new StringReader(document.TextContent));
    FixCompileUnit(unit);
    return unit;
    }

    void FixCompileUnit(CodeCompileUnit unit)
    {
    CodeTypeDeclaration formClass = FindForm(unit);
    FullyQualifyBaseType(formClass);
    }

    static void FullyQualifyBaseType(CodeTypeDeclaration type)
    {
    CodeTypeReference reference = type.BaseTypes[0];
    if (reference.BaseType == "Form") {
    reference.BaseType = "System.Windows.Forms.Form";
    }
    }

    The code above finds the form and then changes the base type so it is fully qualified.

    If we add a control to the form, such as a button, and close and re-open the designer, the loader will fail with a CodeDomSerializerException saying that the variable 'button1' is undeclared. The problem here is that the forms designer needs the button1 field to be added to the code DOM. This is not done by IronPython's PythonProvider class. In the IronPython addin the code DOM generated by the PythonProvider is altered so it can be loaded by the forms designer in the PythonDesignerCodeDomGenerator class. We will not look at that class in detail here but instead return to the other problems we skipped over at the start with the PythonFormsDesignerDisplayBinding class. First the CanAttach method needs to attach only to Python files that are designable. This is done by using the IsDesignable method of the forms designer.

      public bool CanAttachTo(IViewContent content) 
    {
    ITextEditorControlProvider textEditorControlProvider = content as ITextEditorControlProvider;
    if (textEditorControlProvider != null) {
    string fileName = GetFileName(content);
    if (fileName != null && IsPythonFile(fileName)) {
    ParseInformation parseInfo = ParserService.ParseFile(fileName, textEditorControlProvider.TextEditorControl.Text, false);
    return FormsDesignerSecondaryDisplayBinding.IsDesignable(parseInfo);
    }
    }
    return false;
    }

    /// <summary>
    /// Gets the filename from the view content. This method
    /// takes into account the fact that the view content may
    /// be untitled.
    /// </summary>
    string GetFileName(IViewContent viewContent)
    {
    if (viewContent.IsUntitled) {
    return viewContent.UntitledName;
    }
    return viewContent.FileName;
    }

    /// <summary>
    /// Checks the file's extension represents a python file.
    /// </summary>
    static bool IsPythonFile(string fileName)
    {
    string extension = Path.GetExtension(fileName);
    if (extension != null) {
    return extension.ToLower() == ".py";
    }
    return false;
    }

    If we set ReattachWhenParserServiceIsReady to true then the CreateSecondaryViewContent method will need to make sure that it has not already added a forms designer window otherwise we may get two windows added. This can be done by adding the following code at the start of the CreateSecondaryViewContent method.

       foreach (ISecondaryViewContent existingView in viewContent.SecondaryViewContents) { 
    if (existingView.GetType() == typeof(FormsDesignerViewContent)) {
    return new ISecondaryViewContent[0];
    }

    That finishes the overview of how to implement a forms designer, now onto code completion.

    Code Completion

    Code completion is a huge area so we will not cover everything here. The IronPython addin itself only has limited support for code completion, so we will limit ourselves to looking at code completion for import statements and static types. The main classes involved when implementing code completion are shown below.

    Code completion classes diagram

    The DefaultCodeCompletionBinding is the starting point for code completion support. It is responsible for enabling and disabling different types of code completion (e.g. method completion, comment completion). It handles key presses in the text editor, calls the appropriate completion data provider and shows the code completion window.

    The PythonCodeCompletionBinding derives from the DefaultCodeCompletionBinding and adds support for the keywords "from" and "import". This support means that when a space character is typed in after a keyword the CtrlSpaceCompletionProvider is used to show a completion list.

    The CodeCompletionDataProvider returns a list of code completion items at the current location.

    The MethodInsightDataProvider provides information about overloaded methods when the opening bracket of a method is typed in.

    The IndexerInsightDataProvider provides a list of items, for example attribute names, after an opening square bracket is typed in.

    The CtrlSpaceCompletionDataProvider is derived from the CodeCompletionDataProvider class and provides completion when the user presses Ctrl+Space or types in a keyword followed by a space character.

    The ExpressionFinder locates the expression before and around the current cursor position. By default the text editor provides a basic expression finder which can be used whilst you are first implementing code completion. Often you will find you need more control over finding the expression so a custom expression finder can be created. The IronPython addin implements its own custom expression finder.

    The Resolver takes the expression returned from the ExpressionFinder and tries to determine what the expression actually is (e.g. is it a method, a class or a namespace). When it has worked this out it returns a resolve result which can then be used to generate a list of completion data. There is no default implementation for the resolver so the IronPython addin implements its own in the PythonResolver class.

    The starting point for adding code completion is to create a class that implements the ICodeCompletionBinding interface.

     public interface ICodeCompletionBinding 
    {
    bool HandleKeyPress(SharpDevelopTextAreaControl editor, char ch);
    }

    The DefaultCodeCompletionBinding class implements that interface but it also implements a lot of code completion functionality which saves us from doing the same. In the IronPython addin's case we have the PythonCodeCompletionBinding class that derives from the DefaultCodeCompletionBinding. Before we look at this class it is worth noting that it might have been possible to just create a class that implements the ICodeCompletionBinding interface and uses Python's dir function to get basic code completion working. When you are using the interactive interpreter you can type in things like "dir()" or "dir(System.Console)" to see the items that can be used at that point. For now this is left as an exercise for the reader.

    To tell SharpDevelop that we have a code completion binding we add a new CodeCompletionBinding element to the PythonBinding.addin file.

     <!-- The Python code completion binding --> 
    <Path name = "/AddIns/DefaultTextEditor/CodeCompletion">
    <CodeCompletionBinding id="Python"
    extensions=".py"
    class="ICSharpCode.PythonBinding.PythonCodeCompletionBinding"/>
    </Path>

    The first thing we will do is look at how to get code completion when you type in an import statement. To do this the PythonCodeCompletionBinding needs to handle the import keyword, generate the list of imports and then show the list in a window.

     public class PythonCodeCompletionBinding : DefaultCodeCompletionBinding 
    {
    public PythonCodeCompletionBinding()
    {
    }

    /// <summary>
    /// Shows the code completion window if the keyword is handled.
    /// </summary>
    /// <param name="word">The keyword string.</param>
    /// <returns>true if the keyword is handled; otherwise false.</returns>
    public override bool HandleKeyword(SharpDevelopTextAreaControl editor, string word)
    {
    if (word != null) {
    switch (word.ToLowerInvariant()) {
    case "import":
    case "from":
    CtrlSpaceCompletionDataProvider dataProvider = new CtrlSpaceCompletionDataProvider(ExpressionContext.Importable);
    editor.ShowCompletionWindow(dataProvider, ' ');
    return true;
    }
    }
    return false;
    }
    }

    The CtrlSpaceCompletionDataProvider will ask SharpDevelop for the parser that handles the current file extension and then it will ask the parser for a resolver. The CtrlSpaceCompletionDataProvider will then call the resolver's CtrlSpace method. So our PythonParser needs to return a resolver.

      public IResolver CreateResolver() 
    {
    return new PythonResolver();
    }

    The PythonResolver class implements the IResolver interface and for now we will return a array of strings back from the CtrlSpace method.

     public class PythonResolver : IResolver 
    {
    public PythonResolver()
    {
    }

    public ResolveResult Resolve(ExpressionResult expressionResult, int caretLineNumber, int caretColumn, string fileName, string fileContent)
    {
    return null;
    }

    public ArrayList CtrlSpace(int caretLine, int caretColumn, string fileName, string fileContent, ExpressionContext context)
    {
    ArrayList results = new ArrayList();
    results.Add("a");
    results.Add("b");
    results.Add("c");
    return results;
    }
    }

    If the IronPython addin is now compiled you will see the list a, b, c is displayed when typing in the space character after an import statement. To get the correct items in the list we can use the IProjectContent's AddNamespaceContents method as shown below.

      public ArrayList CtrlSpace(int caretLine, int caretColumn, string fileName, string fileContent, ExpressionContext context) 
    {
    ArrayList results = new ArrayList();
    ParseInformation parseInfo = ParserService.GetParseInformation(fileName);
    ICompilationUnit compilationUnit = parseInfo.MostRecentCompilationUnit;
    compilationUnit.ProjectContent.AddNamespaceContents(results, String.Empty, compilationUnit.ProjectContent.Language, true);
    return results;
    }

    When we now press the space character we get better results. Now let us look at getting simple code completion when you type in the dot character after the namespace in an import statement.

    If you look at the DefaultCodeCompletionBinding's HandleKeyPress method you will see that by default when the dot character is typed in the CodeCompletionDataProvider is used to get a list of items for code completion. This code is shown below.

        case '.': 
    if (enableDotCompletion) {
    editor.ShowCompletionWindow(new CodeCompletionDataProvider(), ch);
    return true;
    } else {
    return false;
    }

    The CodeCompletionDataProvider will look for an ExpressionFinder for the current file extension, this will fail since we do not currently have one for Python files, then it will use the TextUtilities GetExpressionBeforeOffset method instead to get the expression before the cursor. This saves us from writing our own expression finder initially but you may well find that it is not good enough and you will probably want to write your own. The IronPython addin has its own custom expression finder because this is the only way to set the expression context type (i.e. is the expression an import or namespace) is at creation in the expression finder class. The expression returned from the TextUtilities class is then passed to the resolver's Resolve method. The resolver then needs to work out what the expression refers to and return code completion information. For now we will cheat and pretend that the expression resolves to the System namespace.

      public ResolveResult Resolve(ExpressionResult expressionResult, int caretLineNumber, int caretColumn, string fileName, string fileContent) 
    {
    return new NamespaceResolveResult(null, null, "System");

    The NamespaceResolveResult used above will return a list of code completion items for the specified namespace. Now when we type in "import System" followed by the dot character we get code completion for the System namespace. What we need to do is check that the expression is an import statement and work out the namespace being used. The correct way of doing this is shown below.

      public ResolveResult Resolve(ExpressionResult expressionResult, int caretLineNumber, int caretColumn, string fileName, string fileContent) 
    {
    // Search for a namespace.
    string ns = GetNamespaceExpression(expressionResult.Expression);
    if (!String.IsNullOrEmpty(ns)) {
    return new NamespaceResolveResult(null, null, ns);
    }
    return null;
    }

    static string GetNamespaceExpression(string expression)
    {
    string ns = GetNamespaceExpression("import ", expression);
    if (ns == null) {
    ns = GetNamespaceExpression("from ", expression);
    }

    if (ns != null) {
    return ns;
    }
    return expression;
    }

    /// <summary>
    /// Removes the "import " or "from " part of a namespace expression if it exists.
    /// </summary>
    static string GetNamespaceExpression(string importString, string expression)
    {
    int index = expression.IndexOf(importString, StringComparison.InvariantCultureIgnoreCase);
    if (index >= 0) {
    return expression.Substring(index + importString.Length);
    }
    return null;
    }

    Now we get completion results for namespaces after an import statement. A side effect of this code is that we also get code completion inside a class when we type in the dot character after a namespace. Let'ss take a look at getting a list of methods for a type such as System.Console. To do this we need to alter the Resolve method so that it looks for a type.

       // Search for a class name. 
    ParseInformation parseInfo = ParserService.GetParseInformation(fileName);
    ICompilationUnit compilationUnit = parseInfo.MostRecentCompilationUnit;
    IClass matchingClass = compilationUnit.ProjectContent.GetClass(expressionResult.Expression);
    if (matchingClass != null) {
    return new TypeResolveResult(null, null, matchingClass);
    }

    First we look for the parse information for the current file, then from the project content we try to match the expression against a class. If this matches we return a TypeResolveResult which will provide the completion data. Note that the code above will only work with fully qualified class names, so typing in Console followed by the dot character will not work, but typing in System.Console will work.

    Well, that is a quick introduction to code completion, for more information take a look at the code for the addins that support code completion (e.g. IronPython addin, Boo Binding addin code, and CSharpBinding addin).

    Code Conversion

    How you implement code conversion will depend on what functionality the parser you are using can give you. With IronPython we can generate code from Microsoft's code DOM so we need a way of converting from SharpDevelop's code DOM to Microsoft's. Microsoft's code DOM does not support all the features of many languages, Python included, so the code conversion will be limited in what it can produce.

    The IronPython addin currently only supports converting code from C# and VB.NET to Python. When the menu option to convert code to Python is selected, the code in the text editor is parsed using the C# or VB.NET parsers, a SharpDevelop code DOM generated, this code DOM is converted to Microsoft's code DOM and then IronPython's PythonProvider is used to generate Python code which is displayed in a new text editor window.

    For now we will look at converting from C# to Python. The code in the IronPython addin will be slightly different since it handles both C# and VB.NET, but since both of these languages will produce a SharpDevelop code DOM the basic concepts will be useful for either of these languages. First we need to add a new menu item for the conversion.

     <Path name="/SharpDevelop/Workbench/MainMenu/Tools/ConvertCode"> 
    <Condition name="ActiveContentExtension" activeextension=".cs" action="Disable">
    <MenuItem id="ConvertToPython"
    insertafter="CSharp"
    insertbefore="VBNet"
    label="Python"
    class="ICSharpCode.PythonBinding.ConvertToPythonMenuCommand"/>
    </Condition>
    </Path>

    This menu item is only enabled when the open file has a C# file extension. The menu class is straightforward. It takes the code, creates a CSharpToPythonConverter class and shows the generated Python code.

     public class ConvertToPythonMenuCommand : AbstractMenuCommand 
    {
    public override void Run()
    {
    // Get the code to convert.
    IViewContent viewContent = WorkbenchSingleton.Workbench.ActiveWorkbenchWindow.ViewContent;
    IEditable editable = viewContent as IEditable;

    // Generate the python code.
    CSharpToPythonConverter converter = new CSharpToPythonConverter();
    string pythonCode = converter.Convert(editable.Text);

    // Show the python code in a new window.
    FileService.NewFile("Generated.py", "Python", pythonCode);
    }
    }

    The CSharpToPythonConverter class uses the NRefactory library so the addin project needs a reference to ICSharpCode.NRefactory. The CSharpToPythonConverter class is fairly large so we will only look at a few of the methods. It works by creating a SharpDevelop code DOM from the source code, walking this code DOM and converting it to Microsoft's code DOM, and then generating Python code using IronPython's PythonProvider.

     public class CSharpToPythonConverter : IAstVisitor 
    {
    public CSharpToPythonConverter()
    {
    }

    /// <summary>
    /// Converts the C# source code to Python.
    /// </summary>
    public string Convert(string source)
    {
    CodeCompileUnit pythonCodeCompileUnit = ConvertToCodeCompileUnit(source);

    // Convert to Python source code.
    return ConvertCodeCompileUnitToPython(pythonCodeCompileUnit);
    }

    /// <summary>
    /// Converts the C# source code to a code DOM that
    /// the PythonProvider can use to generate Python. Using the
    /// NRefactory code DOM (CodeDomVisitor class) does not
    /// create a code DOM that is easy to convert to Python.
    /// </summary>
    public CodeCompileUnit ConvertToCodeCompileUnit(string source)
    {
    // Convert to code DOM.
    CompilationUnit unit = GenerateCompilationUnit(source);

    // Convert to Python code DOM.
    return (CodeCompileUnit)unit.AcceptVisitor(this, null);
    }

    public CompilationUnit GenerateCompilationUnit(string source)
    {
    using (IParser parser = ParserFactory.CreateParser(SupportedLanguage.CSharp, new StringReader(source))) {
    parser.Parse();
    return parser.CompilationUnit;
    }
    }

    public static string ConvertCodeCompileUnitToPython(CodeCompileUnit codeCompileUnit)
    {
    PythonProvider pythonProvider = new PythonProvider();
    StringWriter writer = new StringWriter();
    CodeGeneratorOptions options = new CodeGeneratorOptions();
    options.BlankLinesBetweenMembers = false;
    options.IndentString = "\t";
    pythonProvider.GenerateCodeFromCompileUnit(codeCompileUnit, writer, options);
    return writer.ToString().Trim();
    }

    The code DOM conversion is initiated in the ConvertToCodeCompileUnit method. The AcceptVisiter call causes the SharpDevelop code DOM to be visited part by part. The CSharpToPythonConverter class implements the IAstVisiter interface so as each part of the code DOM is visited the corresponding method on the IAstVisiter class is called.

    The NRefactory library already provides a CodeDomVisitor class that will generate a Microsoft code DOM from SharpDevelop's code DOM. Unfortunately the code DOM generated by this class needed some changes before the PythonProvider would generate good Python code so a custom converter class was created.

    That is the end of this tutorial on language bindings. The full source code for the IronPython addin is available at the end of this tutorial. Instructions on how to compile the addin are in the next section.

    Compiling the IronPython AddIn from Source

    1. Download SharpDevelop's source code.
    2. Extract SharpDevelop's source code from the zip file.
    3. Build SharpDevelop by running either src\debugbuild.bat or src\releasebuild.bat.
    4. Extract the IronPython addin's source code from IronPythonAddIn-0.2.1.src.zip to the folder src\AddIns\BackendBindings.
    5. Open a command prompt and navigate to the Python addin folder src\AddIns\PythonBinding containing the source code just extracted.
    6. Run msbuild PythonBinding.sln to compile the code.