I'm working on a project now that involves quite a bit of cut and paste from an old site into a new one. While doing so, I'm also moving some code from tag based CFCs into script based ones. Obviously you can't just paste tag based code into a script based tag. I wondered if it would be possible to build something to help me with some of the grunt work conversion. Specifically, I wanted something that would change:
<cfset x = 1>
Into:
x = 1;
Turns out it wasn't difficult at all - once I figured out how to work with CFBuilder's selection/editor support for extensions.
First - before going any further, if you haven't read about how to build extensions with CFBuilder, I'd suggest taking a look at the docs first. I'd also check out Simon Free's article on DevNet. I'm not going to cover the entire process, just the portions that handle working with text selection.
So the first thing we need is our ide_config.xml file. I've pasted it below:
<application>
<name>convertToCFScript</name>
<author>Raymond Camden</author>
<version>1</version>
<email>ray@camdenfamily.com</email>
<description>intro.html</description>
<license>license.html</license>
<menucontributions>
<contribution target="editor">
<menu name="Convert to CFSCRIPT">
<action name="Do It" handlerid="convert" showResponse="false"></action>
</menu>
</contribution>
</menucontributions>
<handlers>
<handler id="convert" type="CFM" filename="convert.cfm" />
</handlers>
</application>
My extension will have one menu contribution, and note that it has a target of editor. This tells CFB that my extension works within an opened file, and not the Navigator or RDS section. In this case, I'll have a new menu, Convert to CFScript, with one item, Do It. (FYI, why "Do It"? Currently you can't add an action to the root menu. You can also add a 'folder' with an action underneath it. Obviously thats a bit silly here. I've filed an ER to make this unnecessary.) The action runs my convert handler. Let's look at that now:
<cfset fileName = data.event.ide.editor.file.xmlAttributes.location>
<cfset selectionStartLine = data.event.ide.editor.selection.xmlAttributes.startline>
<cfset selectionStartCol = data.event.ide.editor.selection.xmlAttributes.startcolumn>
<cfset selectionEndLine = data.event.ide.editor.selection.xmlAttributes.endline>
<cfset selectionEndCol = data.event.ide.editor.selection.xmlAttributes.startline>
<cfset selectionText = data.event.ide.editor.selection.text.xmlText> <!--- handle cfsets --->
<cfset newText = rereplaceNoCase(selectionText, "<cfset[[:space:]]+(.?)[[:space:]]>", "\1;", "all")> <cfsavecontent variable="test">
<cfoutput>
<response>
<ide>
<commands>
<command type="inserttext">
<params>
<param key="text"><![CDATA[#newText#]]></param>
</params>
</command>
</commands>
</ide>
</response>
</cfoutput>
</cfsavecontent> <cfheader name="Content-Type" value="text/xml"><cfoutput>#test#</cfoutput>
<cfsetting showdebugoutput="false">
<cfset ideData=form.ideEventInfo>
<cfset data = xmlParse(ideData)>
So first off - when I began development on this little utility, I wasn't sure what the editor was going to send me. I did a bunch of cflogging and after looking at the XML, I saw that I was given:
- The filename (this includes the full path).
- The starting line and column of the selection. I've filed an ER to also include the starting numeric character position.
- The ending line and column of the selection.
- The actual selected text.
I didn't actually need all of these values, but I've kept them in the code for purely educational purposes.
The actual meat of my logic is one regex. As you can see, I look for cfsets and simply replace them with a script equivalent. If I decide to keep working on this extension, I could look at supporting loops and other items, but for now I wanted to keep things simple. I'll also recommend doing what I did - testing my regex in another file. That kept CFBuilder out of the picture and let me fine tune my regex until it works like I wanted.
Finally - we need to tell the editor to replace the selection with my new text. I was stuck here for a while until I got help from Evelin Varghese of Adobe. Turns out there is a bug in the documentation. The docs say to use