As promised in my last post, I’m going to explain just how the CleanupFileAndNamespaces plugin works.
To recap, this plugin creates a new Command (CleanupFileAndNamespaces) which will cause CodeRush to fire the “Organize Namespace References” refactoring followed by the “CleanupFile” command.
So how does it work?
This plugin consists of a single new command. When this command is triggered, it will perform a simple sequence of steps, which together will create the effect we are after.
The high-level steps are:
- Store current location
- Execute CleanupFile command
- Move caret to first using \ Imports directive
- Execute Optimize Namespace References refactoring
- Restore Caret location.
We’re going to look at 2 through 4 first and then revisit 1 and 5 together,
Execute the CleanupFile command
Executing an Command requires that we first know the name of the command we want to execute.
In this case I know that the command I want is called CleanupFile
However if I did not, the best place to check would be the Shortcuts
The Options screen can be found via DevExpress\CodeRush\Options.
The Shortcuts page can be found at IDE\Shortcuts.
On the right of this page is a dropdown labelled Command.
This dropdown contains a list of all the Commands that CodeRush is aware of.
Remember from previous blog posts that everything about CodeRush is dynamic, and so if you create a new action (the internal name for a command), you’ll find that CodeRush will list that action in this dropdown as well.
We’d like to execute a command to cleanup the current file. Looking down through the list of commands, we soon come across several which are prefixed with the phrase Cleanup. They appear to work at a number of different levels (File, Folder, Project and Solution). We’ll pick CleanupFile for our plugin.
Once we know which Command we’d like to execute, we need to get a reference to it in code. Internally commands are known as Actions. So in order to get a reference to the CleanupFile action, we would use code like….
Having got a reference to the action, we can execute it like so….
Now that we know how to execute an action, let’s move on to the slightly more complex task of executing a refactoring.
Position the Caret.
Refactorings are very context sensitive and are typically not available all of the time. In this case the caret must be placed on a using (or imports) directive, before it will be available to use.
In order to locate the first using\imports directive, we will use an ElementEnumerable object.
This class is created with a scope (the Active File) and a LanguageElement Type (NamespaceReference – The internal name for a using or imports directive).
Then we ask this class to enumerate and provide us with the first matching Element it can find.
We use FirstOrDefault(), because it will return a null if no appropriate element is found. This is a sensible precaution, since there may not be any NamespaceReferences in the current file.
Once we have a reference to the NamespaceReference, we can use it’s location as a destination to move to.
Actually moving the caret into position is a simple matter of …
Once in position, we will be able to get a reference to the Refactoring in a very similar manner to that of obtaining a reference to an action
Execute the Refactoring
Again we need to know the name of the refactoring that we’d like to execute. In most cases the name of the refactoring, is the same as the display name of the refactoring, so you can just copy the text that you’re used to seeing when you execute a refactoring from within CodeRush.
In this case the refactoring we want to execute is “Organize Namespace References”
We can get our reference like this…
Refactorings are executed via their Execute method. however we must first check if the refactoring is available. Only once we have confirmed availability, can we execute the refactoring.
Saving and restoring the caret location.
As you have seen, executing the ‘Optimize Namespace References’ refactoring, requires that we move the caret to a using\imports directive in order that it can function. Further, executing the CleanupFile action typically moves he caret to the end of the file which is processed. So no matter which way you look at it, the caret will not remain in its initial location.
We should really do something about this, so let’s store the location of the caret at the start and return it to this location at the end of our action.
We could capture the exact coordinates of the caret, but the we know the file is about to be altered. If anything above our caret’s initial position is removed, or added, then the stored position will be unlikely to make much sense afterwards.
We could also bind the stored coordinates to the code, which would allow them to be adjusted up and down in these cases. however if methods are reordered this will still likely leave us in a strange location.
So what to do? Should we give up and just leave the caret at the top or bottom of the file?
Of course not.
As luck would have it, the latest version of CodeRush provides us with a new subsystem for exactly this sort of situation.
The new NavigationServices subsystem provides us with the means to capture our current location in terms of our place within the hierarchy of code elements.
We get the current location and store it with…
Once we are done with our modifications, we can restore the relative position of the caret with…
Our New Action
So now that we have all of the constituent parts of our plugin, we need a way to trigger their execution.
There are several options available to us: Refactoring, CodeProvider, Command etc.
Whilst one of the pieces used is a Refactoring, the combined effect is not.
One characteristic of Refactorings and CodeProviders is that they present themselves contextually through the CodeRush SmartTag menu. This allows the Refactorings and CodeProviders to indicate when they are available.
Our function would theoretically be available from any position in any C# or VB.net file. This means that it would show up all the time. This in turn would reduce the effectiveness of the SmartTagMenu. Therefore I decided not to implement the new functionality in either of these ways.
Instead I chose to implement a new Action. If you recall, an Action is the internal name for a Command. These commands\actions are bindable to shortcut keys
I created and registered the Action with the following code:
This is pretty much boilerplate stuff aside from the indicated customizations. The first of these is the Name of the new action. This is the value you will look for in the commands drop down on the shortcuts option page. The second of these is a reference to the method to execute when the action is triggered.
The final steps are pretty simple.
Implement the CleanupFileAndNamespaces_Execute function using all the code from the first half of this post.
Then, lastly, be sure to call registerCleanupFileAndNamespace method from within your plugin’s InitializePlugIn method otherwise all of that lovely code, will never run at all. With that, we have all the elements we need and our action is complete.
The full sourcecode for this plugin is available on github, as is the binary version