Creating a DxCore plugin
Coding standards are one of those things that generally involve some degree of compromise, as there is often a goal during collaboration to give the code a uniform look and feel. I don’t necessarily agree with this goal in all cases, but I do understand it. Having code formatted in wildly different styles in the same solution or assembly steepens the learning curve for newcomers to the project and will have some negative effects on maintenance activities.
So, what to do when the group’s agreed-upon coding standards don’t match your own preferences? Most would simply adapt. I’m not really like most, though. I’m an inveterate tinkerer and heavy customizer. In all of my IDEs, I use a black background and arrange the colors just so. This mentality extends to everything from my windows folder views to my coding idioms. Everything that I do, I make a point to do because of conscious deliberation and not simply because I’m used to doing it. There is a method to my madness. So my solution to being asked to adopt a common set of standards was to write an IDE plugin that would convert my idioms to the group’s and vice-versa.
Because I’ve really been enjoying the DxCore set of tools lately, I decided to go this route. Here are the steps that I’ve taken in order to get a plugin up and running. At the time of writing, this plugin is fairly simple. I like to use camel casing in my code, and I distinguish between class fields, method parameters, and local variables by using the following idioms, respectively: _classField, methodParameter, myLocalVariable (properties and methods are ClassProperty and ClassMethod()). My rationale is that this allows me at a glance to tell what each thing is that I’m referring to. The standard of one of the groups in which I’m developing is _ClassField and localVariable (sans my). I don’t prefer this because I can’t distinguish between method parameters and my local variables at a glance, meaning I don’t know whether I own the reference or my caller does. So, the plugin converts all class variables and method variables back and forth between these idioms, and that’s it.
For usability purposes, I created a CodeRush action to which I could bind a shortcut key sequence, and I adorned it with some niceties such as putting it in a menu and showing a BIGHINT when the actions are executed. Since the documentation for the DxCore API is somewhat scattered and not always complete, a fair bit of reverse engineering went into this, so I make no claims that what I’m doing is the preferred way to do things. But it does work. I may refine this and revisit this as I get more familiar with it, but here is what I’ve done so far (using CodeRush/DXCore 10.2.5 and Visual Studio 2010):
Getting started with a new DXCore plugin is pretty straightforward. Open a new Visual Studio project, go to the DevExpress menu, and select “New Plugin.”
From there, give your plugin a descriptive name that suits your purposes and take care of all of the particulars of naming the classes the way you want. Then you’re ready to go. This link describes some steps in detail, including selecting manual rather than automatic loading of the plugin that you’re creating (you probably don’t want Visual Studio scooping this up until it’s tested and stable): Create a plugin. This blog was actually what I referred to for the how-to of creating this.
Lights, Camera, Action
This will create an empty plugin project ready to use. In order to have it do anything useful, you must open the Toolbox from the designer and drag and drop an Action (from the DXCore portion) onto your designer.
Once the action is on your designer page, double click on it. You will be booted into the code behind, ala classic Visual Studio development. Here, an event handler will be created for the action’s execution. This is going to be the plugin’s entry point. We’ll bind the action to a shortcut key sequence in the IDE, and this is the method to which you’ll add code to manipulate the IDE.
For now, if you want to verify that something is happening, I would suggest dropping a “Big Hint” onto your designer, naming it something (I got creative and left it as “bigFeedback1”), and adding the following to your execute event handler:
bigFeedback1.Text = "Hello World!"
Now that you have an actual action and something that it does, you’re ready to try it out. Follow the instructions on Dror Helper’s blog for running the plugin. Basically, you’re going to do a normal run which will, in turn, launch a new instance of Visual Studio. Assuming that you’ve bound a shortcut sequence to your new action, once you load your plugin and execute that sequence, you will see that big CodeRush feedback:
(Clearly, my code does not say “Hello World” at the time of running, but you get the idea.)
One thing to look out for is an exception message when you run. I don’t know if it’s something about my setup specifically, but I see the following:
For me, ignoring this and hitting continue makes everything go fine. YMMV.
The code to make it go
I wrote a good bit of code and some unit tests to make this work. The full, zipped solution (as of this writing) can be downloaded here for anyone interested. I certainly won’t go into everything, especially the logic of modifying the variable names. But I will go over a few concepts with which I struggled a bit and had to learn by trial and error.
The DXCore engine has a very deep inheritance hierarchy, and it’s important to find the right level of object in the hierarchy to get the operations that you want. This API is not yet thoroughly documented, so it can be a bit confusing. The first thing that you’re going to be interested in for my plugin is the ActiveClass. This corresponds to the class that’s currently active in the IDE. ActiveClass is obtained by querying static class CodeRush — specifically, CodeRush.Source.ActiveClass.
ActiveClass exposes IEnumerables of TypeDeclaration called “AllFields”, “AllMethods”, “AllProperties”, etc. that correspond to language elements of interest in the class. However, you don’t want TypeDeclarations — you want LanguageElements. So you’ll want to cast these guys as LanguageElement. Doing so exposes a “NameRange” property, which is what you pass to ActiveClass.Document.SetText(namerange, newname) to change the name of the variable. You’re also going to be interested in LanguageElement’s “FindAllReferences()” method, which returns an IEnumerable of IElements. You’ll need this in order to pass to CodeRush.Source.GetLanguageElement(IElement) so that additional LanguageElements correspond to additional instances of your element besides its declaration.
And, finally, if you want to check certain properties of a language element (is it private, protected, etc., what its parent element is, etc.), you will need to cast it to AccessSpecifiedElement.
This is not incredibly intuitive, so I’m hoping that this helps people — at least by pointing in the right direction of which types and classes to look at in the meta-data or to play with.
Adding your action to menus
This part is relatively straightforward once you know what to do, but it took me a while to figure that out. I was looking to create something like this where I could right-click inside the class and apply my conversions:
So, as it turns out, if you right-click on your action in the designer and display the properties, you can set this all through the properties. I’ve circled the ones that I needed to set in order to achieve the desired effect in the previous screenshot. If you want to create another layer of sub-menuing, I have no idea how this would be achieved. But, I don’t want to do that right now, so that’s that.
If you want to add it to one of the existing menus in Visual Studio like the edit menu or the DevExpress menu, you can do that too.
Switching out of Development Mode
When you’re finished with developing and ready for your plugin to be loaded automatically when Visual Studio and DXCore startup, navigate to the project’s AssemblyInfo.cs and edit the properties of the assembly DXCoreAssembly. Change “PluginLoadType.Demand” to “PluginLoadType.Startup”, and now your plugin will be available for loading at startup. You will still need to configure it in the DevExpress plugin manager.
An interesting thing to note is that DXCore’s plugin creator automatically sets the build output to go in the DevExpress plugins directory (DevExpress\IDE Tools\Community\PlugIns). So if you want to redistribute this guy, you can just grab the DLL from there and email it to your friends, or whatever.
This is very much a work in progress, and I expect to polish this tool more as I use it. I may revisit this post and edit a bit if it needs it or create follow-up posts if there is interest. One thing that comes to mind off the top is that I plan to disable the conversions menu item if the user does not click inside a class. Other nice features might be to play with and set some default settings on the context picker for users to have some guidance when creating keyboard shortcuts.
If anyone has questions, comments, or suggestions for improvement, all are welcome.
Edit: I just now uploaded a slightly modified version of the plugin about a day after this post. I discovered that I local variables were not being modified properly in methods if they were nested in any kind of scoping delimiter, like an “if” statement or a try/catch block. The reason for this was that I was previously checking to see if the AccessSpecifiedElement had a parent of type Method, not realizing that these other scopers were considered logical parents. I modified that code to instead call GetMethod() and check to see if it was null. There’s doubtless some better heuristic for distinguishing locals, but this one seems to work.
Edit2: I have created a new post detailing additional progress on this matter.