Wednesday, December 12, 2007

CreateStubForHandler - How does it work?

Our good friend Koen Hoefkens suggests that there might be some interest around the inner workings of the newly refactored CreateStubForHandler.

Versions
There are 2 versions of the plugin. One for DXCore 2.x (for VS2003) and another for DXCore 3.x (for VS2005 and VS2008).

This was done for 2 reasons:
Koen (the original author) has a few projects kicking around in VS2003, and so we needed to maintain a version of the code capable of producing a version of the plugin that could likewise run under the DXCore 2.x and hence under VS2003. I have a virtual machine setup to run VS2003 under XP and so I am in a position to test this project at my end if need be.

DevExpress have recently released a beta of DXCore 3.0 along with companion copies of RefactorPro and CodeRush. Now these new betas (like the full versions which will follow) only run inside of VS2005 or better but provide some interesting features which are not available in previous versions. Therefore I thought it would be good to create a version of the code which would take advantage of these new features where possible.

So to meet both of these requirements, we have divided the project into 2 parts and therefore 2 directories. The main trunk of the project contains directories called 'VS2003' and 'VS2008'. These house the Solutions and further directories and Projects suited to those environments.

Triggering the plugin
Each Project contains a plugin class created from a template drawn from the appropriate DXCore. These Plugin classes each use techniques and technology again suited to the version of the DXCore that they support. The DXCore 2.x uses an Action and a RefactoringProvider to trigger the plugins main functions, while the VS2008 version uses the same action logic together with a CodeProvider to achieve a similar effect.

The inner workings are the same for each due to their sharing of a common "PluginLogic.vb" file which lives in the VS2003 folder structure and is linked to from the VS2008 solution.

How does it work?
Essentially what happens is that whether activated by Action, RefactoringProvider or CodeProvider, much the same thing happens.

We have a main function "CreateMethodStub" which is passed 3 parameters:

  1. CaretElement: A LanguageElement which represents the element of code next to the caret at the time of activation. We can use a LanguageElement here because we're talking about an object on the sourcecode.
  2. InsertionPoint: A SourcePoint which tells the plugin where the newly generated method should be placed.
  3. CompleteStatement: A boolean indicating whether or not the AddHandler/RemoveHandler statement requires completion.

This function is responsible for organising the various stages of the plugin's operation. You can think of it as the core of this plugin. It delegates to other functions to do almost everything and then triggers the expansion of the code into the Active Document.

Build the New Method
The first step in generating code using the DXCore is to choose the correct root LanguageElement. That would be "Method" in this case since we are trying to create a method.

Next it's all about adding other suitable LanguageElements to that Method in the correct places and waving the "DXCore Magic Code Generation Wand".

Creating a method is easy:
-------------------------------------------------------------
Dim Method As New Method(MethodName)
Method.MethodType = MethodTypeEnum.Void
-------------------------------------------------------------
..Next we need to add some parameters which is normally easy also:
-------------------------------------------------------------
Dim SomeParam as New Param("System.String",ParamNameHere)
-------------------------------------------------------------

Constructing the correct signature
The difficulty here is that we can't just create any old parameters. We have to create parameters suited to the Event for which we are creating our handler method.

So how do work out what those are then?

If you need a quick refresher on why we're using IElement derived interfaces then take a quick peek here

Well we ask the DXCore for the declaration of the Event which we are attempting to attach to like this....
-------------------------------------------------------------
Dim TheDelegate As IDelegateElement = CType(EventDec.Type.GetDeclaration, IDelegateElement)
-------------------------------------------------------------
... the iterate the parameters of said delegate duplicating the params on the method...
-------------------------------------------------------------
For Each Param As IParameterElement In TheDelegate.Parameters
    Dim NewParam As New Param(Param.Type.FullSignature, Param.Name)
    NewParam.Direction = Param.Direction
    Method.Parameters.Add(NewParam)
Next
-------------------------------------------------------------

Add Some Content
Next we need to add some default content to that Method. We have decided to continue the original plan for now, and generate a statement to Throw a NotImplementedException.

So we offload the creation of the ThrowStatement on another simple utility function just so that our code can remain tidy.

Now we could just add this ThrowStatement to our Method.
this would be done by calling...
-------------------------------------------------------------
Dim TheThrow as ThrowStatement = GetThrowStatement("System.NotImplementedException")
Method.AddNode(TheThrow)
-------------------------------------------------------------
...but We'd like to be a bit more clever with our plugin.

Although the Throw statement is a good default, in 90% of cases the first thing you'd like to do once your plugin has finished doing it's thing, is to erase the throw and replace it with something more useful.

So how about we select the ThrowStatement ready to be overtyped, just like in a template.

In order to do this you need to Wrap the code in special Template Elements. If you've every created your own templates, you'll be familiar with «Caret» and «BlockAnchor». They are placed one at either end of your code, and once "Expanded", will cause CodeRush to select the text in the document

There are 2 problems with this.

  • We don't have any code to wrap
  • We don't have any LanguageElements to represent Template Elements.

Hmmmm.....what to do?

Ok it turns out that there is a special LanguageElement for inserting arbitrary text into your code. It's called SnippetCodeElement

So the first implementation of this looked a bit like....
-------------------------------------------------------------
Method.AddNode(New SnippetCodeElement("«Caret»")
Method.AddNode(GetThrowStatement("System.NotImplementedException"))
Method.AddNode(New SnippetCodeElement("«BlockAnchor»"))
-------------------------------------------------------------
But this had the annoying side effect of inserting a CRLF after the Throw and before the «BlockAnchor». In essence the CRLF got selected as well and this just wasn't right.

So a new approach had to be found. So I asked (as I did for much of this) AlexZ (of Developer Express), if he could explain how they did this and I received and embarrassingly simple solution.

Alex suggested pre-generating the code for the ThrowStatement, deliberately removing the trailing CRLF, wrapping the resultant code in the raw Template Element text and finally adding the whole lot to the method as a single SnippetCodeElement thus...

-------------------------------------------------------------
Dim ThrowStatement As [Throw] = GetThrowStatement("System.NotImplementedException")
Dim ThrowWithoutCRLF As String = GenerateWithoutCRLF(ThrowStatement)
Dim NewSnippetCodeElement As New SnippetCodeElement("«Caret»" & ThrowWithoutCRLF & "«BlockAnchor»" & ControlChars.CrLf)
Method.AddNode(NewSnippetCodeElement)
-------------------------------------------------------------

Producing the Code
Finally we wave that "DXCore Magic Code Generation Wand" I mentioned earlier by calling...
-------------------------------------------------------------
CodeRush.Language.GenerateElement(TheMethod)
-------------------------------------------------------------
...and the inserting that code in the correct location in our original document...
-------------------------------------------------------------
CodeRush.Documents.ActiveTextDocument.ExpandText(InsertionPoint, ControlChars.CrLf & MethodCode)
-------------------------------------------------------------

And now we're done. :)

Full source is available via SVN


Updated: the previously existing repository is still intact however the code has been copied to a new repository where future work will be continued from. this new repo can be found here here

Tuesday, December 11, 2007

LanguageElements, LiteElements and IElements

It's important to understand at least 2 out of these 3 terms when creating DXCore plugins.

  • A LanguageElement is a structure that represents a code structure defined in source code.
  • A LiteElement is a Friend (Or internal) class used internally by the DXCore. It represents a code structure defined in IL (like the framework itself). This is the one you don't really need to understand because you can't cast the object itself to this type , because this type in unavailable to you, the humble plugin programmer.
  • An IElement is an interface implemented by the previous 2 structures. It provides common access to properties provided by the previous structures allowing you to code against this interface rather than have to code against the previous 2.

These 3 structures are of course the ultimate parent structures of many derivatives.

So you should write your plugin analysis code in terms of IElement derived interfaces.

Of course you'll need to create LanguageElement derived objects when you need to generate code, but try to use the interfaces for any discovery work you need to do.

If you don't do things this way, then you'll find that you need to use LanguageElements to analyse the source code and Reflection to go looking through the framework classes.

Believe me... You Don't Want To Do This!

Apart from which, using the IElement derived structures in the DXCore is way easier and results in tighter plugin code all around

A small example
So imagine that you have the code...
-------------------------------------------------------------Public Structure SomeStruct
    Private DummyMember As Integer
End Structure
Public Class SomeClass
    Private X As SomeStruct
    Private Y As Integer
End Class
-------------------------------------------------------------
The return value from GetDeclaration for X will be an "IElement" which you can cast back to "Struct" which is a descendant of LanguageElement.

The return value from GetDeclaration for Y will be an "IElement" which you can would be able to cast back to "LiteStructElement", only it's not available from outside of the StructuralParser Assembly.

Both of these can be addressed in a uniform way however, by casting them to IStructElement

Note: The theory goes that since most LanguageElement derivatives will have a matching IElement derivative. you should attempt to work in terms of IElement when trying to analyse code. Simply because you should not assume that that which you are trying to analyse is modifiable.

So hopefully that sorts out any confusion as to why IElement derivatives are used so prolifically in plugins.


Friday, December 07, 2007

Plugin Development under Vista 64-bit

There are a minor issue affecting the development of plugins under 64-bit vista.

The basic issue is that your installation of Visual studio (assuming that you go for the defaults) will be under "C:\Program Files (x86)\" and defaults in a plugin project (and indeed other types of project) will point to a directory under "C:\Program Files\".

Now CodeRush /Refactor/DXCore will all install (by default) also under "C:\Program Files (x86)\" and it seems very hard to convince the OS to allow anything else.

All this means that you will compile and build plugins to a directory ( C:\Program Files\..etc..") which the DXCore is not looking in.

The simplest way I have found to solve this is to use a program like Junction Link Magic to create a "Junction Point" between a new Empty folder created at "C:\Program Files\Developer Express Inc\" and the real folder at "C:\Program Files (x86)\Developer Express Inc\".

A "Junction Point" is a "Magic Portal" between the empty folder and the specified destination. The effect of which, is to fool the FileSystem, and hence every program that makes use of it, into seeing all the files in both locations.

It should be noted that there is only one set of files, they are just visible in 2 places. If you delete anything it will vanish from both locations

So once this is done you can happily compile Dlls to one location and run them through DXCore from the other.


CreateStubForHandler Plugin

Those who have been following this thread in the DevExpress Forums will already be aware of my involvement in a new plugin for VB developers called CreateStubForHandler

This plugin was created by Koen Hoefkens to aid VB.Net developers in their daily tasks.

The CreateStubForHandler plugin does 1 wonderful thing in 2 specific ways.

Refactoring/CodeProvider

Imagine you have this line of code...

-------------------------------------------------------------
AddHandler MyButton.Click, AddressOf MyClickHandler
-------------------------------------------------------------
...but you do not yet have and actual 'MyClickHandler'.

Typically you now have to either have a good memory for the parameters needed for the handler or you have to go trawling through the intellisense/tooltips/Object browser to determine what such a handler should look like.

Well now, with a simple refactoring (CodeProvider in Refactor3.0), you will be presented with a 'Target Picker' which you can use to determine the location that 'CreateStubForHandler' will create just such a handler.

The CodeProvider does all the heavy lifting for you. It creates the procedure and populates the signature with all the correct parameters.

It also fills the procedure with a simple throw exception statement.....
-------------------------------------------------------------
Throw New NotImplementedException()
-------------------------------------------------------------

Action

As an additional bonus, there is also an additional "Action" ("AddHandler completion") which you can map to a key of your choosing. This action should be activated when you have the following code...
-------------------------------------------------------------
AddHandler MyButton.Click
-------------------------------------------------------------
... with your caret on the right of said code. (note the lack of a comma)

Once activated, "AddHandler completion" will not only create the procedure for you but will calculate an appropriate default name for the method, but will fill out the "AddressOf" clause for you

Koen was generous enough to allow his plugin to be added to the DXCorePlugins Open Source project on GoogleCode.
So if you're interested you can get the full source to this plugin as well as a few others.

Great stuff!

Thanks Koen

Note: In truth, my only involvement in the project was a little refactoring of the code itself and the creating of a parallel VS2008 project to house the Refactor 3.0 version of the code

I have not added any functionality to the project (unless you count changing the VS2008 version to use a CodeProvider rather than a RefactoringProvider.)


Wednesday, December 05, 2007

Something, Beta, this way comes.

The 1st beta of CodeRush 3.0, Refactor 3.0 and therefore of course DXCore 3.0 is now available to subscribers of DevExpress' IDE Tools.

NOTE: These will replace version 2.x if installed and support has now been dropped entirely for VS2003 as all libraries are not build using .Net 2.0

Refactor! Vs Code!

imageRefactor has had a makeover and split it's offerings into "Refactor" items and "Code" items. At the moment, the pre-existing Refactor items are available in the Refactor! section. These are the items that take your code and reshape it, improving the readability and reusability without altering the effect it produces. image

Next up is the all new Code! section. The Code! section is for items which, unlike their Refactor siblings, do alter the code they act upon. Items like "Rotate item 90 degrees" act on some selected code and modify it in some way.  To quote Mark Miller:

'Rotate 90 Degrees' is most useful, taking logic that applies in one direction, and rotating it to apply in the other direction (converting Width to Height, X to Y, Left to Top, Right to Bottom, Column to Row, etc.), while 'Mirror Code' is less useful (converting Left to Right, "+" to "-", Top to Bottom, etc).

Other items available in Code! include the "Declare" range. These allow you to generate Classes, Structs, Interfaces, Constructors, Locals, Fields, Properties, Setters and Getters from references.

Code Issues

Refactor has always suffered from a small problem which was that you had to know that a Refactoring could be used before you could use it. So you would place your caret somewhere in code and if Refactorings were possible, then you would see the Refactoring SmartTag which lead to the SmartMenu which would then show you the refactorings that could be applied in the current location.

But if you never placed your caret in the correct location then you would have to rely on past knowledge to tell you where you might be able to refactor.

Code Issues is a promising little piece of architecture which will allow CodeRush to highlight code smells and bring to your attention, those items which could do with a little refactoring.

In the beta there are 2 Code Issues that are  highlighted. 'Undeclared Elements' and 'Unused Declarations'. This means that if you declare a variable or method but do not use it, the declaration with be highlighted and your attention drawn to it.

When I first started to use the new version of CodeRush, imageI thought I was suffering some strange programming glitch. I appeared to have some kind of imagegraphical corruption on the right hand side of my editor. It turns out that this is in fact a graphical map (see right) of the code Issues in the current document (Left).

The purpose of these would seem to be to help further draw your attention to your code issues. I have seen no sign of a Code Issues ToolWindow so far, but then this is the first beta and there are some issue preventing me at this time from accessing some of the more traditional ToolWindow. (CodeRush, Messages, Expression Lab).

As I have said there are only 2 Code Issues Providers so far but another quote from Mark Miller reveals a little about what is to come:

With regard to error checking, the goal is to ultimately implement all of the errors, warning, and hints produced by each of the language compilers we support. With regard to code smells, that's a much broader area, and for the most part we won't be talking about what we'll be addressing there until its released, other than to say that if we have a refactoring or code provider designed to solve a problem, it is highly likely that you'll see us ship a corresponding code issue provider to highlight opportunities to improve the code.

So stay tuned and I'll see what else I can find out for you.


Tuesday, December 04, 2007

My last Vista problem - Solved!

For those not keeping up... The last issue I have with Vista was detailed here.

The basic problem was that administrator programs were not recognising clicks events when they were not the foreground application.

The today I noticed, completely by accident, that My VS (a "Run as Administrator" application) DID recognise a "right click".

How strange....

That set me thinking about click messages and interception and that's when it hit me..... I run a third party App (Nifty Windows) for it's ability to toggle the StayOnTop bit of any window I choose. It does this by intercepting the Win+LClick key/Mouse event.

The problem seems to be that "Nifty Windows" intercepts the left click but is apparently (I'm guessing due to Vista security practices) is unable to send those clicks to any program which is elevated.

The solution: run Nifty Windows Elevated

Oh the relief :D