Disclaimer (Yes again)
Yup, some people still don’t like regions. If that’s you, then you might not be that interested in how a region-appreciative plugin works.
We’re not here to discuss if they’re good or bad. We’re here to show you how the plugin from my previous post does what it does.
Overview
There are only 2 parts to this plugin.
- Expand the Template
- Collapse Regions
Expand the Template
If you’re looking to write an action which needs to be able to expand templates as part of it’s remit, then there really is no better way to get this done, than to call the original command which already takes care of this.
To call another command, you’ll want code like this:
Locate Regions to Collapse
So the next issue is which regions to collapse. You see there might already be regions in the document before your template is expanded, and you don’t necessarily want to collapse those.
Ideally we would like to know the sourceRange into which the template expanded. The TemplateExpand command does not provide this information out of the box, so we’ll have to work it out ourselves. I picked what seemed at the time, like the simplest approach possible.
I calculate the LinesFromStart and LinesFromEnd. (ie the number of lines from these points to the line the caret is on.)
…then these values can be used (together with the size of the post-expansion document) to calculate the size and position of the region into which the template expanded.
Collapse the Regions
Once we have expanded our template, and have a range of code to process, it should be a simple enough procedure to iterate through RegionDirectives in this range and collapse each of them in turn.
Except that it’s unfortunately not that simple.
A Problem of Timing
As indicated, the problem is one of timing.
You see VS is a multi-threaded application. This allows it to delegate certain non-essential tasks to background threads. These task are assumed to be less vital than some others and thus will take something of a back seat if other more processor intensive work is being done.
So what does this mean for us? Well it turns out that fully hydrating new regions added to code is considered a less important task.
You can definitely understand that point of view. The 500ms delay between adding a region and it being considered a real region is nothing right. I bet most of you have never even noticed that small delay between moving off the line after creating a region and the point when VS recognises it as a region and adds outlining to it.
However as indicated this does provide us with a small issue.
If we run our code as suggested so far, we’ll hit a situation where the instruction to collapse a detected region is ignored, because there isn’t yet any sort of outlining associated with the region(s) in question.
So what to do instead?
Well the simplest solution is just to wait until the relevant information is available. I’d prefer to do this via event, but at the time of writing the code, I couldn’t find one that would alert me at the proper time.
So I created a DelayedRunner class (main code below) whose purpose is to repeatedly run a passed func<bool> until such time as that function returns true.
I added a parameter to specify a delay between each execution, and another to provide a maximum length of time, after which the DelayedRunner would turn itself off and essentially give up.
This required that I alter the code of CollapseRegions so that it returned a bool indicating it’s level of success.
Note: If no regions are detected in the range, the routine returns success immediately.
Finally this delayed execution is executed using the following code:
Limitations
The multithreaded nature of this command means that it is not possible to wrap this up in a singe UnitOfWork and add it to the undo stack. Therefore when attempting to undo the effects of this command, the user will have to invoke Ctrl+Z twice to achieve the desired results.
As always the code for this plugin is available on github and the latest release is also available as a VSIX