Thursday, January 28, 2010

Code Completion in Xcode: How to make your own text macros

[Update] There seems to be an issue with text macros in Xcode 3.2.1 (possibly 3.2) on Snow Leopard that when using the "usual" was (as outlined bellow) code completion for text macros stops working. If you delete the HOME/Library/Application Support/Developer/Shared/Xcode folder and relaunch Xcode, Xcode will add the Shared/Xcode folder back and auto completion and text macros come are enabled in the editor. Editing the xctxtmacro file in the application binary file does not enable the newly created text macro in the Xcode text editor either.

We are looking into a fix and will post it as soon as we know more.



Recently Don showed me a little trick to help me with my debugging skills - as I am not a coder by trade but earnestly try my hand at it. It has to do with "compiler directives" used to log the current method being called (with the log being within the scope of the method). It is as follows:

NSLog(@"FUNCTION: %s", __FUNCTION__);

Don then told me about the "compiler directives and that there were three he thought would be useful for me, __LOCATION__, __FILE__ and __LINE__. Which concatenated in the NSLog statement looks like this:

NSLog(@"FILE, FUNCTION, LINE: %s, %s, %i", __FILE__, __FUNCTION__, __LINE__);

Great! I love this, but now I have to remember it - and it's not really going to change. So I looked into the Xcode documentation and found out about text macros.

There are several that are built-in and that I've been using since day one; such as if, ifelse, init, nsma and others, but my favorite being log.

I'm a fan of templates, snippets, code sensing, auto-completion, text macros or anything reusable to help expedite the build process. I wanted to start utilizing this feature within Xcode and need to learn how.

What I found is that it's quite simple to make your own text macros and that you can use all of the predefined Xcode text macros as an excellent starting point. There are a few things to do before we can begin creating our text macro and that is to create the files necessary for Xcode to load them up at launch time.

Creating Xcode user-defined text macro folder and files.
Go to your Xcode application file (root/Developer/Applications/).
Right-click (control-click) and Show Package Contents...
Navigate to (Contents/PlugIns/TextMacros.xctxtmacro/Contents/Resources/).
Select the Objective-C.xctxtmarco file and Copy it (command-c).
Open a new Finder window and select your Home folder.
Navigate to (Library/Application Support/Developer/Shared/Xcode/).
In the Xcode folder Paste (command-v) the Objective-C.xctxtmacro file.

As you can see we have created our working copy of the text macros description file and moved it to it's new home (being sure not to overwrite the original). Now we can dive into our editing and creation of our new text macro.

Creating your Xcode text macro.
The Objective-C.cxtxtmacro is basically a ascii plist so open it with your favorite plist editor. In this case I'm going to use Xcode by dragging the file onto the Xcode icon in my Dock.

As you can see it's an Array with around 26 items, each of which is a Dictionary. Take a look at a few if you'd like as they are a wonderful reference. Also, if you'd like go back to the Xcode package, copy those other text code files elsewhere and look at them. It's always good to have reference/source materials.

We're going to go down to the last one, select it and then click on the "+" symbol/tab to the right of the selected cell. This will add a new Item to the plist root Array which will be our new entry (text macro definition).

Select the new Item and change the Type from String to Dictionary. Now tap on the disclosure triangle (the left side of the selected cell), this will rotate the triangle from pointing right (collapsed) to pointing down (expanded). You may also notice that the "+" symbol to the right changes into a set of lines when the Item is expanded. This let's us add "children" name/value pairs to our new Item.

There are a few "children" name/value pairs we need to add to our new Item to make it function and they are as follows (to the best of my knowledge):
  • Identifier - This describes the macro's language (parent).identifier.
  • BasedOn - This is the (parent) language (objc).
  • IsMenuItem - Boolean value. This creates a menu item in Edit menu.
  • Name - The name listen in the (above) menu item.
  • TextString - The actual string that will be inserted via the text macro.
  • CompletionPrefix - This what you type in as the key for the text macro.
NOTE: These are the 6 that are necessary in Xcode 3.2 and later. I believe in Xcode 3.0.x - 3.1.x you also needed IncludeContexts, which in Xcode 3.2 you can use IncludeContexts (array) and ExcludeContexts (array) to control in which blocks the text macro will show up in the auto-complete list.

Adding the "Names".
In our new Item we're going to add the six new children name/value pairs - all of which are going to be of Type strings. Just click on the tab to the right six times.

Go ahead and name the new children's names as outlined above. You can click on the first one and name it then tap Tab twice to quickly move to the next one (name) beneath it. It should look like this:


Filling in the "Values".
Now comes the fun part - filling in the values. For us, for this example we're focusing on a NSLog text macro, so go ahead and fill in the values with the following:

  • Identifier - objc.flog
  • BasedOn - objc
  • IsMenuItem -YES
  • Name - Function (NSLog)
  • TextString - NSLog(@"FUNCTION: %s", __FUNCTION__);
  • CompletionPrefix - flog
This is to say that when we type "flog" and then press the Escape key (esc) the drop down will present us without function and we can press Tab or Enter to finish the code completion. Your Item should look like this:


Let's enter another one. It is almost the same but contains the File and Line number as well (as mentioned at the start of this article).

NOTE: You can collapse the flog Item we just made, select it, right-click (control-click) and Copy then Paste and it will append a copy flog Item beneath the original flog Item - then it's just a couple of text edits and we're done (in bold).

The values are as follows:
  • Identifier - objc.fflog
  • BasedOn - objc
  • IsMenuItem -YES
  • Name - File, Function, Line (NSLog)
  • TextString - NSLog(@"FILE, FUNCTION, LINE: %s, %s, %i", __FILE__, __FUNCTION__, __LINE__);
  • CompletionPrefix - fflog
You can name your Identifier and CompletionPrefix whatever you'd like, as long as it does not conflict with any existing completion identifier. I use flog for Function Log and fflog for File Function Line Log. Here is what the new fflog Item looks like (under the first flog Item):


Conclusion.
Now simply restart Xcode for your new text macros to take affect. All you need to do is open one of your (Objective-C) projects type in flog and press esc then tab or enter/return - and POW - the entire function is inserted for you with just a couple of keystrokes. Nice, simple and elegant. Thank you Apple for making my coding experience a little less... cumbersome.

This is only the beginning of what you can do with text macros. But as I mentioned in the beginning of the article, I'm not the very best coder. As I learn more tricks or reasons for more complicated macros I will go ahead and blog about them here for you. I hope this helps you as much as it helps me.

Will you set up text macros in your own Xcode development environment? Do you like text macros? Would you like to know more about them and possibly have us do a follow up blog posting on them? Comment below. Don't be shy. We'd like to hear from you. You can also follow us on Twitter. Cheers!

2 comments:

Joe said...

Instead of filling up NSLogs with additional arguments, remedied by text macros, I'm going to implement this solution: http://iPhoneDeveloperTips.com/cocoa/filename-and-line-number-with-nslog-part-ii.html

Using macros, but not filling up the text. Alas I have yet to implement it.

Kevin Bomberry said...

@Joe: Thanks for the link to the article. It is an interesting take on another process. I'm really liking extensibility Xcode has to offer. I'll want to take a jab at Templates in the not so distant future.

Let us know how your implementation of Johns solution goes. Or you can do the both and let John and I both know how both processes went. If you bolg about it I'll read it. (^_^)