Both OS X and iOS tend to have a love-hate relationship with Xcode. Crashing, lacking solid refactoring tools, some UX failures, and so on.

For me, it is/was the lack of a good autocomplete that really ticks me off. Xcode’s autocomplete is prefix-based, so Xcode will only show completions where the start of the completion matches your incomplete word. This is rather frustrating, as the prefix for completion items tends to be the same.

For example, if you wanted to complete NSAccessibilityRoleDescriptionForUIElement, you’d probably go:

  • nsacc<TAB> to get NSAccessibility
  • ro<TAB> to get NSAccessibilityRole
  • des<TAB> to get NSAccessibilityRoleDescription
  • and finally f<TAB> to get NSAccessibilityRoleDescriptionForUIElement

Of course, you can always use arrow keys to scroll through the list at some point to select it, but using arrow keys isn’t a great solution.

It would be great if Xcode supported fuzzy autocompletion (like Sublime Text, AppCode, etc etc). I mentioned this to @alanjrogers one Friday afternoon and he told me I should write an Xcode plugin for it.

Why not?

Step 1: Making a plugin for Xcode 5

I found BlackDog Foundry’s Creating an Xcode 4 Plugin which got me off to a good start where I was able to get code loaded into Xcode 5, with a couple of Xcode 5-specific Info.plist UUID tweaks from KFCocoaPodsPlugin.

Step 2: Figure out what to hook into

With class-dump, I dumped Xcode.app’s classes and started searching for likely places to hook into. I stumbled across the DVTTextCompletion* class cluster which was a good place to start. Before you can use the dumped headers, you need to remove the - (void).cxx_destruct method declaration if it exists. You also need to remove some #import statements that cause the build to fail, like #import <objc/NSObject.h> and AppKit imports.

I added JRSwizzle with CocoaPods and started to poke at some classes I thought would be a good start, like DVTTextCompletionSession.

Unfortunately, this didn’t get me very far. Picking random methods to swizzle was a time consuming process which involved lots of restarting of Xcode. I needed a better way to figure out what I should hook into.

Some googling turned up the NSObjCMessageLoggingEnabled environment variable, where it would log every message send to /tmp/msgSend-<pid>. Unfortunately, starting up Xcode generated about 15 million lines of data (a whopping 659MB!!), so that clearly wasn’t what I was after.

dtrace to the rescue

I found a little dtrace snippet on Stackoverflow which would let you probe message sends of a certain class, which gave me a much better indication of what I should be looking at (after tracing DVTTextCompletionSession), however it only output the method calls without any context.

Further googling turned up an article by Jon Haslam on DTrace and Visualisation, where he described using the flowindent option on dtrace to show a call tree. This was better, but it was limited to showing the method name only unless you used printf to show the class. It was messy and not ideal.

I eventually found this script that essentially reimplemented flowindent but with a better visualisatino of Objective-C method calls.

I modified it to provide better indentation and allow scoping down to a certain class. Script is below:

trace_msg_send.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/sbin/dtrace -s
#pragma D option quiet

unsigned long long indention;
int indentation_amount;

BEGIN {
  indentation_amount = 4;
}

objc$target:$1::entry
{
    method = (string)&probefunc[1];
    type = probefunc[0];
    class = probemod;
    printf("%*s%s %c[%s %s]\n", indention * indentation_amount, "", "->", type, class, method);
    indention++;
}
objc$target:$1::return
{
    indention--;
    method = (string)&probefunc[1];
    type = probefunc[0];
    class = probemod;
    printf("%*s%s %c[%s %s]\n", indention * indentation_amount, "", "<-", type, class, method);
}

Usage: sudo ./trace_msg_send.sh -p <pid of app> <objc class>

This produced a much better looking and easier to understand call tree:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-> -[DVTTextCompletionSession initWithTextView:atLocation:cursorLocation:]
<- -[DVTTextCompletionSession initWithTextView:atLocation:cursorLocation:]
-> -[DVTTextCompletionSession showCompletionsExplicitly:]
    -> -[DVTTextCompletionSession isShowingCompletions]
    <- -[DVTTextCompletionSession isShowingCompletions]
    -> -[DVTTextCompletionSession _ensureCompletionsUpToDate]
        -> -[DVTTextCompletionSession textView]
        <- -[DVTTextCompletionSession textView]
        -> -[DVTTextCompletionSession textView]
        <- -[DVTTextCompletionSession textView]
    <- -[DVTTextCompletionSession _ensureCompletionsUpToDate]
    -> -[DVTTextCompletionSession textView]
    <- -[DVTTextCompletionSession textView]
    -> -[DVTTextCompletionSession setPendingRequestState:]
        -> -[DVTTextCompletionSession readyToShowCompletions]
            -> -[DVTTextCompletionSession filteredCompletionsAlpha]
            <- -[DVTTextCompletionSession filteredCompletionsAlpha]
        <- -[DVTTextCompletionSession readyToShowCompletions]
    <- -[DVTTextCompletionSession showCompletionsExplicitly:]
    -> -[DVTTextCompletionSession isShowingCompletions]
    <- -[DVTTextCompletionSession isShowingCompletions]
    -> -[DVTTextCompletionSession setAllCompletions:]
    <- -[DVTTextCompletionSession setAllCompletions:]
    -> -[DVTTextCompletionSession _prefixForCurrentLocation]
        -> -[DVTTextCompletionSession textView]
        <- -[DVTTextCompletionSession textView]
    <- -[DVTTextCompletionSession _prefixForCurrentLocation]
    -> -[DVTTextCompletionSession _setFilteringPrefix:forceFilter:]
        -> -[DVTTextCompletionSession allCompletions]
        <- -[DVTTextCompletionSession allCompletions]
        -> -[DVTTextCompletionSession _bestMatchInSortedArray:usingPrefix:]
        <- -[DVTTextCompletionSession _bestMatchInSortedArray:usingPrefix:]
        -> -[DVTTextCompletionSession _usefulPartialCompletionPrefixForItems:selectedIndex:filteringPrefix:]
            -> -[DVTTextCompletionSession _commonPrefixForItems:]
                -> -[DVTTextCompletionSession rangeOfFirstWordInString:]
                <- -[DVTTextCompletionSession rangeOfFirstWordInString:]

A quick breakdown of a dtrace probe

A dtrace probe is defined by provider:module:function:name. In the above script, I use objc$target:$1::entry. $target is a macro variable which is filled in by pid passed in by the command line via the -p flag. $1, $2 and so forth are the arguments passed into dtrace.

Example: sudo ./trace_msg_send.sh -p 12345 DVTTextCompletionSession

  • Provider: objc12345
  • Module: DVTTextCompletionSession
  • Function: [everything]
  • Name: entry

This will match all method entry events within DVTTextCompletionSession for pid 12345.

dtrace supports wildcards: * for multiple characters, ? for a single character.

For a more detailed rundown on dtrace, see Hooked on DTrace

Step 3: Find out what Xcode is doing

A quick swizzle of setAllCompletions: showed that Xcode sets the completion list to whatever is autocompletable at a particular scope. On a new line, it would set all the constants, C methods, macros, etc etc, which ended to be about 40,000. When autocompleting on [self on one of my plugin classes, it set the completion list to a much more managable 278.

After some digging, I found that _setFilteringPrefix:forceFilter: did what I expected and was called every time the user typed. A few hours of trial and error later, I managed to hook up Xcode’s own IDEOpenQuicklyPattern class to perform fuzzy filtering of the completions, but I still relied on calling the original fuzzy matching method because otherwise the autocompletion list window would not update correctly. This was adding 30-50ms of execution time on the main thread, which is not ideal, so the next step is to figure out what Xcode is doing behind the scenes to update this.

DVTTextCompletionListWindowController is probably what I wanted to look at, but using the same script wasn’t useful as it was filled with -[DVTTextCompletionListWindowController tableView:objectValueForTableColumn:row:] calls, and it was missing a return call so the indentation kept growing.

I wrote another script that let me filter out the method calls I didn’t want polluting my trace, and added the ability to only trace message sends within a certain method.

trace_within_method_and_filter.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/sbin/dtrace -s
#pragma D option quiet

unsigned long long indention;
int indentation_amount;

BEGIN {
  indentation_amount = 4;
}

/* the : in method selectors must be replaced with ? */
objc$target:DVTTextCompletionSession:-_setFilteringPrefix?forceFilter?:entry
{
    tracing++;
}

objc$target:DVTTextCompletionSession:-_setFilteringPrefix?forceFilter?:return
{
    tracing--;
}

objc$target:DVTTextCompletionList*::entry
/
    tracing > 0 &&
    &probefunc[1] != "tableView:willDisplayCell:forTableColumn:row:" &&
    &probefunc[1] != "tableView:objectValueForTableColumn:row:"
/
{
    method = (string)&probefunc[1];
    type = probefunc[0];
    class = probemod;
    printf("%lu %*s%s %c[%s %s]\n", timestamp, indention * indentation_amount, "", "->", type, class, method);
    indention++;
}

objc$target:DVTTextCompletionList*::return
/
    tracing > 0 &&
    &probefunc[1] != "tableView:willDisplayCell:forTableColumn:row:" &&
    &probefunc[1] != "tableView:objectValueForTableColumn:row:"
/
{
    indention--;
    method = (string)&probefunc[1];
    type = probefunc[0];
    class = probemod;
    printf("%lu %*s%s %c[%s %s]\n", timestamp, indention * indentation_amount, "", "<-", type, class, method);
}

I added timestamps to the result because the output kept ending out of order.

I ran with sudo ./trace_within_method_and_filter.sh -ppidof xcode> output.txt, then fixed the order and removed timestamps with cat output.txt | sort -n | cut -c 17-200.

Output with calls to stuff to properties (window, session, and showingWindow) with no inner method calls removed for brevity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
-> -[DVTTextCompletionListWindowController initWithSession:]
<- -[DVTTextCompletionListWindowController initWithSession:]
-> -[DVTTextCompletionListWindowController showWindowForTextFrame:explicitAnimation:]
    -> -[DVTTextCompletionListWindowController window]
        -> -[DVTTextCompletionListWindowController windowDidLoad]
            -> -[DVTTextCompletionListWindowController _loadColorsFromCurrentTheme]
                -> -[DVTTextCompletionListWindowController _iconShadow]
                <- -[DVTTextCompletionListWindowController _iconShadow]
                -> -[DVTTextCompletionListWindowController numberOfRowsInTableView:]
                <- -[DVTTextCompletionListWindowController numberOfRowsInTableView:]
            <- -[DVTTextCompletionListWindowController windowDidLoad]
        <- -[DVTTextCompletionListWindowController window]
        -> -[DVTTextCompletionListWindowController numberOfRowsInTableView:]
        <- -[DVTTextCompletionListWindowController numberOfRowsInTableView:]
        -> -[DVTTextCompletionListWindowController _updateSelectedRow]
            -> -[DVTTextCompletionListWindowController tableViewSelectionDidChange:]
            <- -[DVTTextCompletionListWindowController _updateSelectedRow]
            -> -[DVTTextCompletionListWindowController _updateCurrentDisplayState]
                -> -[DVTTextCompletionListWindowController _getTitleColumnWidth:typeColumnWidth:]
                    -> -[DVTTextCompletionListWindowController _preferredWindowFrameForTextFrame:columnsWidth:titleColumnX:]
                    <- -[DVTTextCompletionListWindowController _preferredWindowFrameForTextFrame:columnsWidth:titleColumnX:]
                <- -[DVTTextCompletionListWindowController _updateCurrentDisplayState]
                <- -[DVTTextCompletionListWindowController _updateSelectedRow]
                -> -[DVTTextCompletionListWindowController setHideReason:]
                    -> -[DVTTextCompletionListWindowController _usefulPrefixAttributes]
                    <- -[DVTTextCompletionListWindowController _usefulPrefixAttributes]
                    -> -[DVTTextCompletionListWindowController _updateInfoNewSelection]
                        -> -[DVTTextCompletionListWindowController showInfoForSelectedCompletionItem]
                            -> -[DVTTextCompletionListWindowController _selectedCompletionItem]
                            <- -[DVTTextCompletionListWindowController _selectedCompletionItem]
                            -> -[DVTTextCompletionListWindowController showInfoPaneForCompletionItem:]
                                -> -[DVTTextCompletionListWindowController _selectedCompletionItem]
                                <- -[DVTTextCompletionListWindowController _selectedCompletionItem]
                                -> -[DVTTextCompletionListWindowController _updateCurrentDisplayStateForQuickHelp]
                                    -> -[DVTTextCompletionListWindowController _usefulPrefixAttributes]
                                    <- -[DVTTextCompletionListWindowController _usefulPrefixAttributes]
                                <- -[DVTTextCompletionListWindowController showInfoPaneForCompletionItem:]
                            <- -[DVTTextCompletionListWindowController showInfoForSelectedCompletionItem]
                        <- -[DVTTextCompletionListWindowController showWindowForTextFrame:explicitAnimation:]

The call stack is missing some return traces and I’m not sure why yet, but we’ve found some relevant methods: _updateCurrentDisplayState and _updateSelectedRow. I wasn’t having issues with the row updating, so I grabbed a reference to DVTTextCompletionListWindowController with [self _listWindowController] and call -_updateCurrentDisplayState. This solved the display issue!

dtrace is pretty awesome, but editing dtrace scripts was annoying. If I have to do more of this, I’d probably write a wrapper using ruby-dtrace to help automate filtering etc.

FuzzyAutocomplete Plugin for Xcode

The end product is FuzzyAutocomplete (github.com/chendo/FuzzyAutocompletePlugin), which works in Xcode 5. It shouldn’t conflict with any existing plugins like KSImageNamed as most plugins expose additional completion items rather than change the filtering.

You can install it with Alcatraz or by cloning it and building it yourself. See the project on Github for more info.

Both OS X and iOS tend to have a love-hate relationship with Xcode. Crashing, lacking solid refactoring tools, some UX failures, and so on.

For me, it is/was the lack of a good autocomplete that really ticks me off. Xcode’s autocomplete is prefix-based, so Xcode will only show completions where the start of the completion matches your incomplete word. This is rather frustrating, as the prefix for completion items tends to be the same.

For example, if you wanted to complete NSAccessibilityRoleDescriptionForUIElement, you’d probably go:

  • nsacc<TAB> to get NSAccessibility
  • ro<TAB> to get NSAccessibilityRole
  • des<TAB> to get NSAccessibilityRoleDescription
  • and finally f<TAB> to get NSAccessibilityRoleDescriptionForUIElement

Of course, you can always use arrow keys to scroll through the list at some point to select it, but using arrow keys isn’t a great solution.

It would be great if Xcode supported fuzzy autocompletion (like Sublime Text, AppCode, etc etc). I mentioned this to @alanjrogers one Friday afternoon and he told me I should write an Xcode plugin for it.

Why not?

Step 1: Making a plugin for Xcode 5

I found BlackDog Foundry’s Creating an Xcode 4 Plugin which got me off to a good start where I was able to get code loaded into Xcode 5, with a couple of Xcode 5-specific Info.plist UUID tweaks from KFCocoaPodsPlugin.

Step 2: Figure out what to hook into

With class-dump, I dumped Xcode.app’s classes and started searching for likely places to hook into. I stumbled across the DVTTextCompletion* class cluster which was a good place to start. Before you can use the dumped headers, you need to remove the - (void).cxx_destruct method declaration if it exists. You also need to remove some #import statements that cause the build to fail, like #import <objc/NSObject.h> and AppKit imports.

I added JRSwizzle with CocoaPods and started to poke at some classes I thought would be a good start, like DVTTextCompletionSession.

Unfortunately, this didn’t get me very far. Picking random methods to swizzle was a time consuming process which involved lots of restarting of Xcode. I needed a better way to figure out what I should hook into.

Some googling turned up the NSObjCMessageLoggingEnabled environment variable, where it would log every message send to /tmp/msgSend-<pid>. Unfortunately, starting up Xcode generated about 15 million lines of data (a whopping 659MB!!), so that clearly wasn’t what I was after.

dtrace to the rescue

I found a little dtrace snippet on Stackoverflow which would let you probe message sends of a certain class, which gave me a much better indication of what I should be looking at (after tracing DVTTextCompletionSession), however it only output the method calls without any context.

Further googling turned up an article by Jon Haslam on DTrace and Visualisation, where he described using the flowindent option on dtrace to show a call tree. This was better, but it was limited to showing the method name only unless you used printf to show the class. It was messy and not ideal.

I eventually found this script that essentially reimplemented flowindent but with a better visualisatino of Objective-C method calls.

I modified it to provide better indentation and allow scoping down to a certain class. Script is below:

trace_msg_send.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/sbin/dtrace -s
#pragma D option quiet

unsigned long long indention;
int indentation_amount;

BEGIN {
  indentation_amount = 4;
}

objc$target:$1::entry
{
    method = (string)&probefunc[1];
    type = probefunc[0];
    class = probemod;
    printf("%*s%s %c[%s %s]\n", indention * indentation_amount, "", "->", type, class, method);
    indention++;
}
objc$target:$1::return
{
    indention--;
    method = (string)&probefunc[1];
    type = probefunc[0];
    class = probemod;
    printf("%*s%s %c[%s %s]\n", indention * indentation_amount, "", "<-", type, class, method);
}

Usage: sudo ./trace_msg_send.sh -p <pid of app> <objc class>

This produced a much better looking and easier to understand call tree:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-> -[DVTTextCompletionSession initWithTextView:atLocation:cursorLocation:]
<- -[DVTTextCompletionSession initWithTextView:atLocation:cursorLocation:]
-> -[DVTTextCompletionSession showCompletionsExplicitly:]
    -> -[DVTTextCompletionSession isShowingCompletions]
    <- -[DVTTextCompletionSession isShowingCompletions]
    -> -[DVTTextCompletionSession _ensureCompletionsUpToDate]
        -> -[DVTTextCompletionSession textView]
        <- -[DVTTextCompletionSession textView]
        -> -[DVTTextCompletionSession textView]
        <- -[DVTTextCompletionSession textView]
    <- -[DVTTextCompletionSession _ensureCompletionsUpToDate]
    -> -[DVTTextCompletionSession textView]
    <- -[DVTTextCompletionSession textView]
    -> -[DVTTextCompletionSession setPendingRequestState:]
        -> -[DVTTextCompletionSession readyToShowCompletions]
            -> -[DVTTextCompletionSession filteredCompletionsAlpha]
            <- -[DVTTextCompletionSession filteredCompletionsAlpha]
        <- -[DVTTextCompletionSession readyToShowCompletions]
    <- -[DVTTextCompletionSession showCompletionsExplicitly:]
    -> -[DVTTextCompletionSession isShowingCompletions]
    <- -[DVTTextCompletionSession isShowingCompletions]
    -> -[DVTTextCompletionSession setAllCompletions:]
    <- -[DVTTextCompletionSession setAllCompletions:]
    -> -[DVTTextCompletionSession _prefixForCurrentLocation]
        -> -[DVTTextCompletionSession textView]
        <- -[DVTTextCompletionSession textView]
    <- -[DVTTextCompletionSession _prefixForCurrentLocation]
    -> -[DVTTextCompletionSession _setFilteringPrefix:forceFilter:]
        -> -[DVTTextCompletionSession allCompletions]
        <- -[DVTTextCompletionSession allCompletions]
        -> -[DVTTextCompletionSession _bestMatchInSortedArray:usingPrefix:]
        <- -[DVTTextCompletionSession _bestMatchInSortedArray:usingPrefix:]
        -> -[DVTTextCompletionSession _usefulPartialCompletionPrefixForItems:selectedIndex:filteringPrefix:]
            -> -[DVTTextCompletionSession _commonPrefixForItems:]
                -> -[DVTTextCompletionSession rangeOfFirstWordInString:]
                <- -[DVTTextCompletionSession rangeOfFirstWordInString:]

A quick breakdown of a dtrace probe

A dtrace probe is defined by provider:module:function:name. In the above script, I use objc$target:$1::entry. $target is a macro variable which is filled in by pid passed in by the command line via the -p flag. $1, $2 and so forth are the arguments passed into dtrace.

Example: sudo ./trace_msg_send.sh -p 12345 DVTTextCompletionSession

  • Provider: objc12345
  • Module: DVTTextCompletionSession
  • Function: [everything]
  • Name: entry

This will match all method entry events within DVTTextCompletionSession for pid 12345.

dtrace supports wildcards: * for multiple characters, ? for a single character.

For a more detailed rundown on dtrace, see Hooked on DTrace

Step 3: Find out what Xcode is doing

A quick swizzle of setAllCompletions: showed that Xcode sets the completion list to whatever is autocompletable at a particular scope. On a new line, it would set all the constants, C methods, macros, etc etc, which ended to be about 40,000. When autocompleting on [self on one of my plugin classes, it set the completion list to a much more managable 278.

After some digging, I found that _setFilteringPrefix:forceFilter: did what I expected and was called every time the user typed. A few hours of trial and error later, I managed to hook up Xcode’s own IDEOpenQuicklyPattern class to perform fuzzy filtering of the completions, but I still relied on calling the original fuzzy matching method because otherwise the autocompletion list window would not update correctly. This was adding 30-50ms of execution time on the main thread, which is not ideal, so the next step is to figure out what Xcode is doing behind the scenes to update this.

DVTTextCompletionListWindowController is probably what I wanted to look at, but using the same script wasn’t useful as it was filled with -[DVTTextCompletionListWindowController tableView:objectValueForTableColumn:row:] calls, and it was missing a return call so the indentation kept growing.

I wrote another script that let me filter out the method calls I didn’t want polluting my trace, and added the ability to only trace message sends within a certain method.

trace_within_method_and_filter.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/sbin/dtrace -s
#pragma D option quiet

unsigned long long indention;
int indentation_amount;

BEGIN {
  indentation_amount = 4;
}

/* the : in method selectors must be replaced with ? */
objc$target:DVTTextCompletionSession:-_setFilteringPrefix?forceFilter?:entry
{
    tracing++;
}

objc$target:DVTTextCompletionSession:-_setFilteringPrefix?forceFilter?:return
{
    tracing--;
}

objc$target:DVTTextCompletionList*::entry
/
    tracing > 0 &&
    &probefunc[1] != "tableView:willDisplayCell:forTableColumn:row:" &&
    &probefunc[1] != "tableView:objectValueForTableColumn:row:"
/
{
    method = (string)&probefunc[1];
    type = probefunc[0];
    class = probemod;
    printf("%lu %*s%s %c[%s %s]\n", timestamp, indention * indentation_amount, "", "->", type, class, method);
    indention++;
}

objc$target:DVTTextCompletionList*::return
/
    tracing > 0 &&
    &probefunc[1] != "tableView:willDisplayCell:forTableColumn:row:" &&
    &probefunc[1] != "tableView:objectValueForTableColumn:row:"
/
{
    indention--;
    method = (string)&probefunc[1];
    type = probefunc[0];
    class = probemod;
    printf("%lu %*s%s %c[%s %s]\n", timestamp, indention * indentation_amount, "", "<-", type, class, method);
}

I added timestamps to the result because the output kept ending out of order.

I ran with sudo ./trace_within_method_and_filter.sh -ppidof xcode> output.txt, then fixed the order and removed timestamps with cat output.txt | sort -n | cut -c 17-200.

Output with calls to stuff to properties (window, session, and showingWindow) with no inner method calls removed for brevity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
-> -[DVTTextCompletionListWindowController initWithSession:]
<- -[DVTTextCompletionListWindowController initWithSession:]
-> -[DVTTextCompletionListWindowController showWindowForTextFrame:explicitAnimation:]
    -> -[DVTTextCompletionListWindowController window]
        -> -[DVTTextCompletionListWindowController windowDidLoad]
            -> -[DVTTextCompletionListWindowController _loadColorsFromCurrentTheme]
                -> -[DVTTextCompletionListWindowController _iconShadow]
                <- -[DVTTextCompletionListWindowController _iconShadow]
                -> -[DVTTextCompletionListWindowController numberOfRowsInTableView:]
                <- -[DVTTextCompletionListWindowController numberOfRowsInTableView:]
            <- -[DVTTextCompletionListWindowController windowDidLoad]
        <- -[DVTTextCompletionListWindowController window]
        -> -[DVTTextCompletionListWindowController numberOfRowsInTableView:]
        <- -[DVTTextCompletionListWindowController numberOfRowsInTableView:]
        -> -[DVTTextCompletionListWindowController _updateSelectedRow]
            -> -[DVTTextCompletionListWindowController tableViewSelectionDidChange:]
            <- -[DVTTextCompletionListWindowController _updateSelectedRow]
            -> -[DVTTextCompletionListWindowController _updateCurrentDisplayState]
                -> -[DVTTextCompletionListWindowController _getTitleColumnWidth:typeColumnWidth:]
                    -> -[DVTTextCompletionListWindowController _preferredWindowFrameForTextFrame:columnsWidth:titleColumnX:]
                    <- -[DVTTextCompletionListWindowController _preferredWindowFrameForTextFrame:columnsWidth:titleColumnX:]
                <- -[DVTTextCompletionListWindowController _updateCurrentDisplayState]
                <- -[DVTTextCompletionListWindowController _updateSelectedRow]
                -> -[DVTTextCompletionListWindowController setHideReason:]
                    -> -[DVTTextCompletionListWindowController _usefulPrefixAttributes]
                    <- -[DVTTextCompletionListWindowController _usefulPrefixAttributes]
                    -> -[DVTTextCompletionListWindowController _updateInfoNewSelection]
                        -> -[DVTTextCompletionListWindowController showInfoForSelectedCompletionItem]
                            -> -[DVTTextCompletionListWindowController _selectedCompletionItem]
                            <- -[DVTTextCompletionListWindowController _selectedCompletionItem]
                            -> -[DVTTextCompletionListWindowController showInfoPaneForCompletionItem:]
                                -> -[DVTTextCompletionListWindowController _selectedCompletionItem]
                                <- -[DVTTextCompletionListWindowController _selectedCompletionItem]
                                -> -[DVTTextCompletionListWindowController _updateCurrentDisplayStateForQuickHelp]
                                    -> -[DVTTextCompletionListWindowController _usefulPrefixAttributes]
                                    <- -[DVTTextCompletionListWindowController _usefulPrefixAttributes]
                                <- -[DVTTextCompletionListWindowController showInfoPaneForCompletionItem:]
                            <- -[DVTTextCompletionListWindowController showInfoForSelectedCompletionItem]
                        <- -[DVTTextCompletionListWindowController showWindowForTextFrame:explicitAnimation:]

The call stack is missing some return traces and I’m not sure why yet, but we’ve found some relevant methods: _updateCurrentDisplayState and _updateSelectedRow. I wasn’t having issues with the row updating, so I grabbed a reference to DVTTextCompletionListWindowController with [self _listWindowController] and call -_updateCurrentDisplayState. This solved the display issue!

Editing dtrace scripts was annoying. If I have to do more of this, I’d probably write a wrapper using ruby-dtrace to help automate filtering etc.

FuzzyAutocomplete Plugin for Xcode

The end product is FuzzyAutocomplete (github.com/chendo/FuzzyAutocompletePlugin), which works in Xcode 5. It shouldn’t conflict with any existing plugins like KSImageNamed as most plugins expose additional completion items rather than change the filtering.

You can install it with Alcatraz or by cloning it and building it yourself. See the project on Github for more info.

I decided to write a command line tool with RubyMotion to visualise CGRects and NSRects from lldb, but I quickly discovered that RubyMotion does not support this out of the box.

You can build an OS X app that doesn’t present a UI by by setting LSUIElement to true and not creating any UI, but when you copy the binary out of the bundle and try to run it, you get an error that looks something like this:

1
drawrect[59471:707] No Info.plist file in application bundle or no NSPrincipalClass in the Info.plist file, exiting

This is due to the NSApplicationMain function not being able to find an Info.plist due to the binary no longer residing inside a bundle. We need to tweak RubyMotion’s compile process to skip this method.

Replacing NSApplicationMain

I tracked down the offending snippet of code within lib/motion/project/template/osx/config.rb.

config.rb link
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
main_txt << <<EOS
int
main(int argc, char **argv)
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
EOS
    if ENV['ARR_CYCLES_DISABLE']
      main_txt << <<EOS
    setenv("ARR_CYCLES_DISABLE", "1", true);
EOS
    end
    main_txt << <<EOS
    RubyMotionInit(argc, argv);
    NSApplication *app = [NSApplication sharedApplication];
    [app setDelegate:[NSClassFromString(@"#{delegate_class}") new]];
EOS
    if spec_mode
      main_txt << "SpecLauncher *specLauncher = [[SpecLauncher alloc] init];\n"
      main_txt << "[[NSNotificationCenter defaultCenter] addObserver:specLauncher selector:@selector(appLaunched:) name:NSApplicationDidFinishLaunchingNotification object:nil];\n"
    end
    main_txt << <<EOS
    NSApplicationMain(argc, (const char **)argv);
    [pool release];
    rb_exit(0);
    return 0;
}
EOS

As you can see on line 139, it calls NSApplicationMain which is the source of the error we’re seeing as NSApplicationMain tries to load the NIB as well as construct the application object. See Matt Gallager’s article on NSApplicationMain for more details.

Patching RubyMotion’s compile step

We could do this one of two ways:

  • Create a new OS X command line template
  • Monkey-patch the Config class

Given I’m kinda lazy, monkey-patching the config class was the way to go for me :)

Make a directory called lib in the root of the RubyMotion project, and make a file named osx_cli.rb.

We need to patch the main_cpp_file_txt method of the Motion::Project::OSXConfig class, so your file should look something like this:

osx_cli.rb
1
2
3
4
5
6
7
module Motion::Project
  class OSXConfig < XcodeConfig
    def main_cpp_file_txt(spec_objs)

    end
  end
end

Then you want to paste the original method body in, and then we can start patching!

Let’s remove the NSApplicationMain call as we won’t be needing that (yet).

Command line applications don’t need an application delegate either, so let’s delete the following lines:

1
2
    NSApplication *app = [NSApplication sharedApplication];
    [app setDelegate:[NSClassFromString(@"#{delegate_class}") new]];

However, we still need an entry point into our Ruby code. Let’s use the pre-existing delegate_class variable to define the entry point class.

osx_cli.rb
65
66
67
68
69
70
71
main_txt << <<EOS
    [[NSClassFromString(@"#{delegate_class}") new] main];
    [pool release];
    rb_exit(0);
    return 0;
}
EOS

Now rather than calling NSApplicationMain, we’re now calling the main instance method on the application delegate class (which is AppDelegate by default).

To make RubyMotion actually pick our code up, we need to require it in the Rakefile.

Rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding: utf-8 -*-
$:.unshift("/Library/RubyMotion/lib")
require 'motion/project/template/osx'
require_relative 'lib/osx_cli'

begin
  require 'bundler'
  Bundler.require
rescue LoadError
end

Motion::Project::App.setup do |app|
  # Use `rake config' to see complete project settings.
  app.name = 'drawrect'
end

When you run your app, it should now call AppDelegate#main!

What if I still need a run loop?

I thought I was home scot-free at this point, however when I tried to initialize a NSWindow in the daemon component of drawrect, I ran into these errors:

1
2
3
4
5
6
7
8
9
10
drawrect[61361:707] _NXCreateWindowWithStyleMask: error setting window property (1000)
drawrect[61361:707] error [1000] setting colorSpace to DELL 3008WFP colorspace
drawrect[61361:707] PSsetwindowlevel, error setting window level (1000)
drawrect[61361:707] _NSSetWindowTag, error clearing window tags (1000)
drawrect[61361:707] _NSSetWindowTag, error setting window tags (1000)
drawrect[61361:707] error [1000] getting window resolution
drawrect[61361:707] Error [1000] setting resolution to 1
drawrect[61361:707] _NSShapePlainWindowWithOpaqueRect: error setting window shape (1000)
drawrect[61361:707] CGSAddSurface failed - error 1000 (windowID:4017)
drawrect[61361:707] CGSAddSurface failed - error 1000 (windowID:4017)

These errors are occuring because the app no longer has a connection to the window server since we have removed the [NSApplication sharedApplication] line, which connects the app to the window server.

All we need to do is call NSApplication.sharedApplication and run the main event loop:

app_delegate.rb link
66
67
68
69
70
71
72
73
74
75
def bootServer!
    app = NSApplication.sharedApplication
    app.delegate = self

    NSApp.run
  end

  def applicationDidFinishLaunching(notification)
    DrawRect.new.listen
  end

And boom, everything works! drawrect is now a self-contained command line application that has the ability to create windows and doesn’t have to reside in an app bundle.

Check out the drawrect project at https://github.com/chendo/drawrect for a fully functioning example.

Let me know if this has been useful to you!

Halfway through debugging the new enumeration engine for Shortcat, I realised I needed a way to visualise CGRect/NSRects while debugging. Trying to figure out how one CGRect relates to another by looking at four numbers is a PITA.

I decided to write a tool to solve this problem. I also took the chance to actually develop an app with RubyMotion.

Introducing drawrect

drawrect is a command line tool for OS X that simply draws translucent rectangles on the screen.

Screenshot

You can use it from within lldb (provided you installed the scripts) or from the command line.

Usage

Within lldb

1
2
3
4
5
6
7
8
9
dr <rect expression>     # This will get the string representation of
                         # the rect returned by the expression and draw it

dr window.frame          # This will draw a rect of the window's frame

drf <rect expression>    # This is the same as above, but where the origin
                         # of the rect is from the top left

dc                       # Removes all the rects

Command line

1
2
3
4
5
6
7
8
drawrect rect <rect> [label] [colour] [opacity]           # Draws a rect at <rect> coordinates with [label], background colour in hex [colour] with opacity [opacity]
drawrect flipped_rect <rect> [label] [colour] [opacity]   # Same as above, but from top-left origin

drawrect rect 100,100,100,100 Hello 00ff00 0.3            # Draws a rect at origin 100,100 with size 100,100, text "Hello", translucent green background
drawrect clear     # Clears rect
drawrect quit      # Quits the drawrect process
drawrect help      # Shows help
drawrect version   # Shows version

Future improvements

I couldn’t find a way to hook into process termination or resuming in lldb, so you would have to clear the rects manually with drc in lldb. If someone knows how to do this, please let me know in the comments!

I’d like to add a feature where you could hover over a CGRect or NSRect in the Variable view and it would highlight, but I’m not sure how to do this without resorting to Accessibility.

I hope drawrect is useful to others! Let me know if this has been useful to you.

Edit – Octorber 9, 2013: Updated the script to support iOS Simulator (i386) and iPhone 5S (ARM 64bit).

Xcode has the ability to break on all Objective-C exceptions (Debug > Breakpoints > Create Exception Breakpoint...), which is extremely useful as you can see the exception before the stack unwinds.

However, enabling this option will cause Xcode to break whenever an exception occurs (even when inside a @try/@catch block), making it hard to track down the exception you’re actually after when you’re working with exception-heavy frameworks like CoreData or Accessibility. Also, Cocoa frameworks sometimes throws exceptions internally, which can be confusing as you wouldn’t see an exception at your code otherwise.

This was annoying me today while I was working on Shortcat as Accessibility really likes throwing exceptions, so I wrote a lldb script that lets you easily specify what exceptions to ignore based on any selector on NSException. I based it off Rob Mayoff’s sniff_objc_exception_throw script as there wasn’t really much in way of lldb documentation.

Setup

  • Grab the script from here
  • Put this script somewhere (eg. ~/Library/lldb/ignore_specified_objc_exceptions.py)
  • Add this to your ~/.lldbinit: command script import <path to ignore_specified_objc_exceptions.py>

But I’m lazy!

for-the-lazy.sh
1
2
3
mkdir -p ~/Library/lldb
curl https://gist.github.com/chendo/6759305/raw/ignore_specified_objc_exceptions.py > ~/Library/lldb/ignore_specified_objc_exceptions.py
echo "command script import ~/Library/lldb/ignore_specified_objc_exceptions.py" >> ~/.lldbinit

Usage

  • In Xcode, add a breakpoint to catch all Objective-C exceptions
  • Edit the breakpoint and add a Debugger Command with the following command: ignore_specified_objc_exceptions name:NSAccessibilityException className:NSSomeException
  • This will ignore exceptions where NSException -name matches NSAccessibilityException OR -className matches NSSomeException

It should look something like this:

Screenshot

Let me know if you have any issues!

I’ve always hated editing files on a remote machine over SSH. I’m not a huge fan of vim, and nano is terrible for anything remotely (hah, get it?) complex.

Being able to use your local editor with all your plugins and everything all set up would be ideal. However, each solution tends to have its caveats.

SFTP

You can use SFTP to access the server with an app like Transmit and then editing the file.

Pros

  • Easiest solution
  • Able to browse directory tree

Cons

  • Generally limited to your user – editing root-owned files requires logging in as root
  • Non-ideal workflow – switch out of the shell to navigate to the file in the app

SSHFS/FUSE

You can mount a server over SSH with something like FUSE for OS X, then editing the file by navigating the mounted filesystem.

Pros

  • Able to browse directory tree
  • Fairly easy to set up

Cons

  • Generally limited to your user – editing root-owned files is difficult
  • Can be slow – Finder tends to enumerate metadata you don’t care about
  • Non-ideal workflow – switch out of the shell to navigate to the file in the app

rsub

rsub is a package for Sublime Text 2 which is a port of rmate functionality in Textmate 2. It utilises a script installed on the remote machine which communicates with your local machine over a remote forwarded port.

Pros

  • Ideal workflow – initiate file editing from shell
  • Usable with sudo – sudo rsub /etc/nginx/conf/nginx.conf works!

Cons

  • More effort to set up – rsub needs to be installed on each machine
  • Not ideal for servers where untrusted users have access

Setting up rsub

Please note that the rsub package only supports Sublime Text 2 at this point.

  1. Install rsub with Package Control
  2. Set up ssh port forwarding:
    • You need to set up a remote forward on port 52698 (by default)
    • You can do it every time you log in:
      1
      
      ssh username@server -R 52698:localhost:52698
      
    • Or you can configure .ssh/config:
      1
      2
      3
      4
      
      # You should only do this for servers that you only have
      # access to as anyone on the server can connect to your machine
      Host *.my-company.com
        RemoteForward 52698 127.0.0.1:52698
  3. Install the rsub remote script on your server:
    1
    2
    3
    4
    
    # aurora's fork of rmate is a bash script that only works with Linux servers.
    # See https://github.com/textmate/rmate for a ruby version
    sudo wget -O /usr/local/bin/rsub https://raw.github.com/aurora/rmate/master/rmate
    sudo chmod +x /usr/local/bin/rsub
    
  4. Use it!
    1
    
    sudo rsub /etc/nginx/conf/nginx.conf
    

About six months ago, I watched Peter Cooper’s Ruby Trickshots and learnt that Ruby has an interesting syntax that allows you to concatenate strings by simply placing them after each other.

For example:

1
"foo" "bar" # => "foobar"

I quickly realised that that meant that this was valid syntax:

1
"""foo"""   # => "foo"

Which meant that Python-like docstrings were syntactically valid in Ruby!

I hacked up a bit of code that let you read out the docstring in this gist:

It was an interesting hack, but I thought that Ruby would have to evaluate the concatenation on every method invocation, hence adding a performance hit purely to add a docstring, and hence ditched the idea of taking it further, and then promptly forgot about it.

Six months later, I found it when I was going through my old gists. I brought it up with CRuby Master @charliesome, and he informed me that CRuby is pretty smart and actually won’t emit bytecode if it detects that the literals aren’t actually used, so there is actually no performance hit (apart from when it’s parsing).

Proof:

https://eval.in/36676 Link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def no_docstring
  nil
end

def with_docstring
  """Foo"""
  nil
end

puts RubyVM::InstructionSequence.of(method(:no_docstring)).disasm
puts "---"
puts RubyVM::InstructionSequence.of(method(:with_docstring)).disasm

# Output:
# == disasm: <RubyVM::InstructionSequence:no_docstring@/tmp/execpad-ecb5745fbd46/source-ecb5745fbd46>
# 0000 trace            8                                               (   1)
# 0002 putnil
# 0003 trace            16                                              (   3)
# 0005 leave
# ---
# == disasm: <RubyVM::InstructionSequence:with_docstring@/tmp/execpad-ecb5745fbd46/source-ecb5745fbd46>
# 0000 trace            8                                               (   5)
# 0002 putnil
# 0003 trace            16                                              (   8)
# 0005 leave

I made a gem called docstrings that allows accessing docstrings via Method#docstring. I’m not entirely sure how useful this actually is, so let me know if you end up using it.

I’m testing parts of Shortcat (which as of v0.4.0 is in Objective-C) with RubyMotion, which is pretty nifty since I’m much more comfortable in Ruby than Objective-C and writing tests is way easier in Ruby. However, it’s far from the fully-featured RSpec like I’m used to.

When working on a class, I like to only run specs for that particular file to keep the runtimes down, then run all the specs later. In RubyMotion, you can achieve this with:

1
$ rake spec files=foo_spec,spec/bar_spec.rb

(via http://www.rubymotion.com/developer-center/articles/testing/#_run_selected_spec_files

However, I couldn’t see any obvious way to run individual specs, which is useful when trying to focus on a particular spec. So I dug into the spec.rb in /Library/RubyMotion/lib/motion/, which seems to be just a copy of MacBacon, and found this:

/Library/RubyMotion/lib/motion/bacon.rb
1
2
3
4
5
6
7
8
9
module Bacon
  ...

  RestrictName    = //  unless defined? RestrictName
  RestrictContext = //  unless defined? RestrictContext

  Backtraces = true  unless defined? Backtraces

  ...
Read on →

While developing Shortcat, I ran into an extremely annoying problem with LSUIElement apps: They crash silently, giving the user no indication that it has crashed. This is especially bad when you don’t have any visual indicator that it’s running.

This post on StackOverflow says that you can use CrashReporterPrefs.app to enable developer mode so it would show the dialog when a backgrounded app crashes. However, this is still a bad experience for end users.

My first attempt at fixing this was to install my own signal handlers so it would bring the application into the foreground, remove the handler, then reraise the signal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void BringAppToForeground()
{
    ProcessSerialNumber psn = { 0, kCurrentProcess };
    TransformProcessType(&psn, kProcessTransformToForegroundApplication);
}

void SignalHandler(int signal) {
    NSLog(@"Signal caught: %d", signal);
    BringAppToForeground();

    // Restore default handler
    struct sigaction action;
    action.sa_handler = SIG_DFL;
    action.sa_flags = 0;
    sigemptyset(&action.sa_mask);
    sigaction(signal, &action, NULL);

    raise(signal);
}

void InstallSignalHandlers()
{
    struct sigaction action;
    action.sa_handler = &SignalHandler;
    action.sa_flags = 0;
    sigemptyset(&action.sa_mask);

    sigaction(SIGABRT, &action, NULL);
    sigaction(SIGILL, &action, NULL);
    sigaction(SIGSEGV, &action, NULL);
    sigaction(SIGFPE, &action, NULL);
    sigaction(SIGBUS, &action, NULL);
    sigaction(SIGTRAP, &action, NULL);
}

// Call InstallSignalHandlers while initialising the app

This worked well. When the app crashed, the dock icon showed up for a second, then the crash dialogue showed up.

However, I noticed that the HockeyApp crash reporting was no longer working, probably due to PLCrashReporter also using signal handlers for its own crash handling and I was overriding them.

This is what I came up with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void BringAppToForeground()
{
    ProcessSerialNumber psn = { 0, kCurrentProcess };
    TransformProcessType(&psn, kProcessTransformToForegroundApplication);
}

static int fatal_signals[] = {
    SIGABRT,
    SIGBUS,
    SIGFPE,
    SIGILL,
    SIGSEGV,
    SIGTRAP
};

static int n_fatal_signals = (sizeof(fatal_signals) / sizeof(fatal_signals[0]));
static struct sigaction original_handlers[6]; // Must set this to the same number of fatal_signals

void SignalHandler(int signal) {
    BringAppToForeground();

    // Restore PLCrashreporter's handlers
    for (int i = 0; i<n_fatal_signals; i++) {
        sigaction(fatal_signals[i], &original_handlers[i], NULL);
    }

    raise(signal);
}

void InstallSignalHandlers()
{
    struct sigaction action;
    action.sa_handler = &SignalHandler;
    action.sa_flags = 0;
    sigemptyset(&action.sa_mask);

    struct sigaction default_action;
    default_action.sa_handler = SIG_DFL;
    default_action.sa_flags = 0;
    sigemptyset(&default_action.sa_mask);


    // Store the original handlers
    struct sigaction original_handler;
    for (int i = 0; i<n_fatal_signals; i++) {
        if (sigaction(fatal_signals[i], NULL, &original_handler) == 0) {
            original_handlers[i] = original_handler;
        }
        else {
            original_handlers[i] = default_action;
        }
        sigaction(fatal_signals[i], &action, NULL);
    }
}

Ideally, it would be better if PLCrashReporter supported some sort of callback so you can perform your own actions before it crashes, but I’d say a lot of people would try to call Objective-C code within it, making the crash handling not as reliable.

Please let me know if there’s ways to improve on this!