Archive for the 'Programming' Category

More Darcs win: Selective recording

Wednesday, December 12th, 2007

Darcs lets you choose which changes you want to include in a patch (commit). For example:

darcs rec                                                   %~/ASL test apps/aslsearch(0)
move ./aslsearch.m ./aslsearch.c
Shall I record this change? (1/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch.c 1
-#import <Foundation/Foundation.h>
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <vis.h>
-#include <asl.h>
-
Shall I record this change? (2/?)  [ynWsfqadjkc], or ? for help: n
hunk ./aslsearch.c 29
-       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
Shall I record this change? (3/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch.c 75
-       [pool release];
Shall I record this change? (4/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch.c 194
+       fprintf(stderr, "key: \e2\80\9c%s\e2\80\9d\noperator: 0x%x\nvalue: \e2\80\9c%s\e2\80\9d\n", key, operator, value);
Shall I record this change? (5/?)  [ynWsfqadjkc], or ? for help: n
hunk ./aslsearch.xcodeproj/project.pbxproj 10
-               8DD76F9A0486AA7600D96B5E /* aslsearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.m */; settings = {ATTRIBUTES = (); }; };
+               8DD76F9A0486AA7600D96B5E /* aslsearch.c in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.c */; settings = {ATTRIBUTES = (); }; };
Shall I record this change? (6/?)  [ynWsfqadjkc], or ? for help: ?
How to use record...
y: record this patch
n: don't record it
w: wait and decide later, defaulting to no

s: don't record the rest of the changes to this file
f: record the rest of the changes to this file

d: record selected patches, skipping all the remaining patches
a: record all the remaining patches
q: cancel record

j: skip to next patch
k: back up to previous patch
c: calculate number of patches
h or ?: show this help

<Space>: accept the current default (which is capitalized)
hunk ./aslsearch.xcodeproj/project.pbxproj 10
-               8DD76F9A0486AA7600D96B5E /* aslsearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.m */; settings = {ATTRIBUTES = (); }; };
+               8DD76F9A0486AA7600D96B5E /* aslsearch.c in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.c */; settings = {ATTRIBUTES = (); }; };
Shall I record this change? (6/?)  [ynWsfqadjkc], or ? for help: f
hunk ./aslsearch_Prefix.pch 5
-#ifdef __OBJC__
-#      import <Foundation/Foundation.h>
-#endif
-
Shall I record this change? (10/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch_Prefix.pch 8
+#include <unistd.h>
Shall I record this change? (11/?)  [ynWsfqadjkc], or ? for help: n
What is the patch name? Switched source file from Obj-C to pure C
Do you want to add a long comment? [yn]n
Finished recording patch 'Switched source file from Obj-C to pure C'

(Before you mention the UTF-8 sequences that Darcs called out as invalid ASCII: Yes, I know.)

And an answer: What to do if your table view shows the whole array in every row

Sunday, December 9th, 2007

Before writing my previous post, I had asked some people on IRC for help, including David Smith, who is a fellow Adium developer. He remembered a solution, but uncertainly. One thing he said, though, proved relevant: “You have two NSTableColumns; which one is it [NSTableView] creating the content binding from?”

David was referring to the content binding of NSTableView, which I had dismissed as an artifact of an earlier design of NSTableView, or perhaps a simplified path for those people who had only one column in their table view. (I saw the mention in the docs that the table view binds it automatically to the value of one of its columns' value bindings, but didn't make any connections then.)

This latter explanation made the content binding incompatible with my app, since I had two parallel arrays of NSStrings (one array contained keys, and the other contained values). So I continued looking for a solution, and eventually gave up and wrote the post.

After I published it, Scott Anguish posted a comment on it:*

you can't have simple arrays of strings as the content of an array controller. it doesn't work. You need to have an array of objects of some sort that have an attribute, even if they're just NSDictionaries with a single attribute called "string".

I don't believe that NSArrayController has a special case for NSString, but Scott's comment got me thinking. I added a category to NSString that added a -selfString method, then added that name as the model key path in the table columns' value bindings. I was wondering, you see, whether this would be enough to satisfy NSTableColumn and NSArrayController.

It still didn't work. At this point, I had the idea to fire up F-Script Anywhere and have a look around. I did that, and was digging down to the table columns when I noticed this:

That's FSA's object browser, showing the table view. So the table view's content binding was bound to the keys array controller, with a model key path of lastMessageKeys.

That's why the keys were showing up fine—they were in the array that was in the table view's content binding. The values array was a different array with a different array controller, so NSTableView threw up its hands and simply showed the entire array's description.

This is the point that Scott came close to: NSTableView expects you to have one array of model objects—no more. My parallel arrays of NSStrings do not qualify, because only one of the array controllers can be the value of the table view's content binding.

So what role do the columns play? Well, each row in the table view (and by that, I mean *the whole row*) corresponds to one of those model objects. The value in each column is then determined by the model key path in the value binding of the corresponding NSTableColumn.

Thus, all the NSTableColumns' value bindings must be bound to the same array controller and the same controller key, and each column should be bound to a different model key path. Your columns' value bindings should look like this:

Column “Foo”Column “Bar”
Bind to: Your one array controller
Controller key: arrangedObjects (or whatever)
Model key path: fooModel key path: bar

In my case, of course, this required creating a new model class for a key-value pair, to replace the parallel arrays of NSStrings. This is a good thing, since it brings conceptual purity to my design. Remember, Cocoa is designed around MVC; I didn't have enough M in my app, which was the problem.

So, in summary, here's the fix for an NSTableColumn that shows the entire array in every row:

  1. Replace your evil parallel arrays with a model class, and a single array of instances of that class.
  2. Delete all but one of your array controllers, since you now have only one array. Update the remaining controller's model key path to point to your new array (I assume you renamed it).
  3. Bind all of the table columns to your new One True Array Controller, distinguishing the columns only by the model key path, which identifies which property you want the column to show for each model object.

Many thanks to both David and Scott for their help in discovering the root of the problem.

* Scott posted his comment on the previous post with no last name; he identified himself in a comment on this post.

I have a question

Saturday, December 8th, 2007

See also the follow-up: And an answer: What to do if your table view shows the whole array in every row.

Normally, on this blag, I give answers.

This time, I have a question.

Here's my problem:

I have a window containing a table view. The table view has two columns, labeled “Keys” and “Values”.

Each column's value binding is bound to an NSArrayController; each array controller's contentArray binding is bound to a property of my application delegate; each property's value is an NSArray of NSStrings.

The Keys column correctly has one of its strings in each row. The Values column incorrectly has its array in every row. This is the problem that has stumped me.

The object class is NSString in both array controllers. There's no Core Data in this app.

I have 12-tuple-checked the bindings in IB; they are the same between the two array controllers and between the two table columns.

The accessors are automatically generated by my accessor-generator services. Even so (having added some debug logging previously), I double-checked them, and they are the same.

I have logged the contents of both arrays in my setters. Both arrays are arrays of strings, and both contain the same number of strings.

This problem manifests on both Tiger and Leopard.

Here are screenshots of my Bindings Inspectors:

The array controllers' contentArray bindings are bound to the app delegate's lastMessageKeys and lastMessageValues properties. There is no value transformer in use. The options that are turned on are Conditionally Sets Editable and Raises For Not Applicable Keys; the options that are turned off are Always Presents Application Modal Alerts, Deletes Objects On Remove, Handles Content As Compound Value, Selects All When Setting Content, and Validates Immediately.

The table columns' value bindings are bound to the array controllers' arrangedObjects properties. There is no value transformer in use. The options that are turned on are Allows Editing Multiple Values Selection, Conditionally Sets Editable, Creates Sort Descriptor, and Raises For Not Applicable Keys; the options that are turned off are Always Presents Application Modal Alerts, Conditionally Sets Enabled, Continuously Updates Value, and Validates Immediately.

Each screenshot has two Inspectors superimposed on each other, with the upper Inspector set to half opacity; the result is that pixels that are the same look normal, whereas pixels that differ look ghostly. For your ease of reading, I knocked out the parts that differ and staggered them, so you don't have to try to make out ghostly text.

Any suggestions?

A possible solution to the stolen-focus problem

Wednesday, December 5th, 2007

Jeff Atwood posted an article today titled Please Don't Steal My Focus, about dialog boxes coming up while you're typing.

The problem, in a nutshell, is that these dialog boxes steal keyboard focus. Sometimes it's for a text field; sometimes it's just for Full Keyboard Access (i.e., a button gets the focus—usually, these are alert boxes). Regardless of what control gets keyboard focus, it gets it by stealing it from your app, which is a problem if the user is typing when that happens. Most likely, either some text will go to the wrong place, or the dialog box will beep for every keystroke; either result will annoy the user.

We've had the same problem for a long time in Adium: we'll throw up a dialog box, but the user is typing a message, and notices that half his message isn't in the inputline. We believe we've fixed most of these, but we still get complaints about this problem from time to time.

Currently, we solve the problem by simply ordering the dialog front without making it key. This works for most people, but it doesn't help people who really do need Full Keyboard Access, since you have to click on the dialog box to dismiss it (or make it key) if it isn't key. We (by which I mean Mac programmers, not just Adium) need a better solution.

I propose an addition to NSAlert. Here's how it would work:

  1. From the creation of the NSAlert instance, it watches for key events.
  2. When any key goes down, the alert resets the timer (if one is present) and sets a flag.
  3. When all keys are up, the alert creates and starts the timer (with an interval of, say, a couple of seconds).
  4. If you tell the alert to show, it checks the flag. If the flag is clear, it simply performs makeKeyAndOrderFront: like usual. If the flag is set, it only orders front, and it sets a second flag.
  5. When the timer fires, the alert clears the first flag, and if the second flag is set, makes itself key.

From the user's perspective, this results in the alert waiting a couple of seconds for them to pause in their typing before it takes keyboard focus.

Does anybody know of an existing free implementation of this, or plan to write it themselves?

From svn to darcs: Listing unknown files

Sunday, December 2nd, 2007

If you're an svn user looking at darcs, you may wonder how to find unknown files—that is, files that you've created in your working directory, but haven't yet told your VCS about (with svn add or darcs add). In svn, the command is svn st, but in darcs, the procedure is not obvious.

The darcs command that most seems like what you need is darcs whatsnew, or, as I type it, simply darcs what. Like svn st, this shows you all files that have been added, removed, or modified; it also includes a diff, so not only is it your substitute for svn st, it's also your substitute for svn diff. But it doesn't show you unknown files.

Until you use the -l (that's a lowercase L) option.

darcs what -l changes the output greatly, as it's in a completely different format: one similar to that of svn st. The format is actually that of darcs what -s (summary mode); the difference is that -l also lists unknown files.

There are some differences between darcs what -l and svn st. Some of these differences are:

  • darcs uses fewer columns than svn: It has one status column, whereas svn has six.
  • darcs uses ‘a’ to indicate an unknown file, whereas svn uses ‘?’.
  • darcs uses ‘R’ to indicate a file that you've removed, whereas svn uses ‘D’ to indicate a file that you've deleted. (svn supports remove and rm as synonyms for its delete command, but darcs does not support delete nor rm as synonyms for its remove command.)

Thanks

Friday, November 30th, 2007
  • To Brent Simmons and Mike Lee.
  • To Matt Drance, who gave me a ride back to my hotel tonight.
  • To Colin Barrett, who gave me a ride back to my hotel (more or less) on Tuesday. (We did not go directly to the hotel.)
  • To Bill Bumgarner, who brought 33 pounds of delicious pork. I think our row of tables needed two more to seat all the people who came for it.
  • To Blake C., who is still the biggest fan of this blag.
  • To everybody, who knew exactly what I was talking about every time I said “blag”. I loved that.
  • To Wil Shipley and Brent for picking up a couple of tabs.
  • To Brent, for supplying me with Yellow Cab's phone number, so that I could call us a cab back to the hotel. Also for paying the fare.
  • To John Geleynse for the invitation, and to Evan Schoenberg for forwarding it on.
  • To everybody who helped me with stuff at the event (including, but not limited to, Deric Horn, Michael Jurewitz, and Peter Ammon).

Let me know if you did something for me this week and I forgot to thank you, or if you want me to remove you from the list for some reason.

Why you should use spaces before your method arguments

Monday, November 12th, 2007

You can select a rectangle that intersects the spaces. You can't do this with tabs.

Backstory: I had moved the method being called there from a file of NSCalendarDate additions to a file to NSDate additions, so I needed to remove “Calendar”, and wanted to also take out the now-extraneous alignment spacing. One rectangular edit did the job, thanks to using spaces for the alignment.

Of course, you should always use tabs for that initial indent.

darcs is becoming my favorite VCS

Friday, October 26th, 2007

Here's a real-world situation that tells you why I'm falling in love with darcs.

I'm working on a plug-in that logs all notifications posted by Apple Mail to a couple of files. The idea of this plug-in is to enable Mail-bundle developers to develop more reliable plug-ins by listening for notifications rather than posing as Mail's classes or swizzling its methods.

(As you know, I don't ordinarily announce what I'm working on before it's ready; for this post, it's important to provide context.)

The first revision of the plug-in used NSLog, but I found that this spammed console.log rather heavily. So I changed it to use two separate log files, which I write to using NSFileHandles.

Then I started work on a new feature: datestamps. While working on that, however, I noticed that I had forgotten to close and release my two file handles in -dealloc. Oops.

Now, remember that I had local changes. If this code were versioned in svn, I would have to:

  1. bzip2 the source file (which renames it as a side-effect).
  2. svn up the file in order to restore the pristine copy.
  3. Open it in a separate editor (so as not to lose undo history) and add the closeFile and release messages that I'd forgotten.
  4. Save and exit.
  5. Commit.
  6. Delete the committed file and bunzip2 my original one.
  7. Go through an svn diff and redo the fix in my modified file so I don't clobber the changes in my next commit.

What a hassle. But darcs has first-class support for just this situation.

All I had to do was darcs amend-record. darcs asked me whether the most recent commit was the one I wanted to amend, then it offered me the choice of which hunks of local changes I wanted to include in the amendment. I said yes to the one with the closeFile and release messages and no to all the others. That's all I had to do.

Let me summarize that for you: I was able to retroactively fix my previous commit while I had other local changes in the same file, without sweeping up those other changes into the commit.

That's awesome.

Generating all combinations from an arbitrary number of sequences

Monday, October 22nd, 2007

In Python 2.5:

import itertools

def combinations(*seqs):
    def base_combinations(so_far, seq):
        for x in seq:
            yield so_far + [x]
    def nested_combinations(so_far, seqs):
        if len(seqs) == 1:
            return base_combinations(so_far, seqs[0])
        else:
            iterators = (nested_combinations(so_far + [x], seqs[1:]) for x in seqs[0])
            return itertools.chain(*iterators)
    return nested_combinations([], seqs)

Usage:

for x, y, z in combinations(xs, ys, zs):
    # Perform magic

If you have a better way, I'd like to read it.

(Thanks to Colin for inspiring me to write it, and renaming one of the nested functions.)

How to make a custom bundle act as a file

Sunday, October 14th, 2007

Recently, Gus Mueller had a blog post about a new plug-in for his image editor Acorn. One thing I noticed is that the plug-in is delivered as a folder:

The plug-in is a bundle, but since it shows up in Finder as a folder, users can navigate into it and poke around. It's unlikely that anything really serious will come of it, but you never know what the average user will wreck when he or she goes exploring in the internals of something.

Fortunately, there are two solutions.

The ad-hoc solution: Turn on the bundle bit

The Mac OS X Xcode Tools include a program called SetFile that, among other things, lets you set the bundle bit on an item:

% SetFile -a B ImageIO\ Export.acplugin

Note that case matters here: If we use an uppercase B, the bit is set; if we use a lowercase b, the bit is cleared.

Those of you who came from the Classic Mac OS may remember that the bundle bit originally indicated that a file contained a 'BNDL' resource (which simplified scanning a folder full of applications to see which ones had some icons to display). I doubt Mac OS X uses it for that purpose anymore.

Its new purpose is for directories: Finder will check for the bundle bit, and if it finds it, it will list the directory as a package.

Packages are directories that Finder treats as files (such as .app bundles). That's exactly what we want. Here's how the plug-in appears after running the above command:

(Note that to get the change to show in Finder, you have to either quit the Finder—such as by logging out—or use AppleScript. I use a command-line tool I wrote that calls -[NSWorkspace noteFileSystemChanged:] with the path to the changed item.)

The permanent solution: Define all items of this type as packages

In the current version of Acorn, it's up to Jens Ayton and other plug-in authors to set the bundle bit on their plug-ins. I think a lot of host apps have this problem.

However, a host app can take care of this for all its plug-ins. All it has to do is have a declaration of its plug-in type (in Acorn's case, that's .acplugin) in its Info.plist, with LSTypeIsPackage set to true in the declaration.

Then, plug-in authors don't need to do anything, because all plug-ins of that type will be shown in the Finder as packages, simply because the declaration in the host app's Info.plist says so. No more setting the bundle bit; in fact, that bit is then ignored.

What do different VCSs do when a file changes?

Thursday, October 11th, 2007

Here's the scenario:

  1. You change a file.
  2. You invoke your-vcs-here commit file.txt (or equivalent).
  3. You write a commit message in the editor.
  4. You suspend the editor with ^z and make another change to a file. (Perhaps you started the commit and a build at the same time, and the build failed because you forgot a comma.)
  5. You resume the editor, and issue :wq or ^x^c to save and exit.

What happens?

Subversion

svn commits the changed version of the file—in other words, the file as it existed when the editor exited.

Monotone

mtn notices that the file changed, tells you “you can't do that”, and throws away saves the commit message.

More precisely, it says:

mtn: misuse: file 'Test.txt' modified during commit, aborting

darcs

darcs commits the file as it existed before it started the editor. (Presumably, it copies changed files to a staging area rather than simply committing them from the WC.)

Mercurial

hg commits the file as it existed before it started the editor, just as darcs does.


UPDATE 2008-03-05: Jack, in a comment below, corrected me on what Monotone does with the commit message. I have applied this correction above.

UPDATE 2008-03-26: jpc, in a comment below, corrected me on what Mercurial does with the commit message. I thought that that's what I had written, but I wrote the wrong thing: that it acted like svn, rather than darcs. I apologize for the brainfart, and I have applied this correction above.

I’ve been interviewed!

Monday, October 8th, 2007

I don't ordinarily post to my main blog with a link, but this isn't my average link. ☺

Jonathan Allen (also known as grauenwolf on reddit) interviewed me about how I do code review on Adium, after I mentioned it in a comment thread on programming.reddit.

In the interview, I talk about why you should review your fellow developers' code and how it works on Adium. I say it's worth reading for anybody who works on a software project with other people.

Notes on Sapiens

Tuesday, October 2nd, 2007

Sapiens, which I found on the DFLL, is a new app launcher that serves as a mouse-based counterpart to Quicksilver.

(Of course, Quicksilver can do a lot more than launch applications. Sapiens can't, so it's a counterpart in only the app-launching aspect.)

One thing I noticed is that when it's running, it gets two Dock tiles:

One of them is the application bundle as I see it in the Finder, which is what I added to the Dock myself, and which is no longer running; the other is the application behind the running Sapiens process, which appears in the Dock for only that reason.

That's if you add it to the Dock from the Finder, of course. That's what I did. If you just drag the running process into place, you'll have only one tile.

The reason for this weird behavior is that Sapiens is actually two applications: The front-end app (which, I guess, just checks whether you've run Sapiens before and shows you the intro movie if you haven't seen it yet), and the real app (which runs in the background and is the real app-launcher app).

Speaking of the intro movie:

The Resources folder for the front-end app contains the introductory movie as a Shockwave Flash (SWF) file, and an HTML file to display it.

What?

Seriously, this is a Mac application. QuickTime is always available, and it's a lot easier to put a QuickTime movie into a QTMovieView (just type the name into the field in IB) than it is to put a Flash movie into a WebView. (And I don't think you even need the HTML file. You could just load the SWF file itself into the WebView.)

The idea is cool, and the app seems to implement it well enough. If I see any other weirdness, I'll add it here.

Growl Registration Dictionary Editor

Monday, October 1st, 2007

Since 0.7, Growl has been able to detect a registration dictionary in your application when your app launches—all you have to do is put it in a file with a certain name inside your app bundle. We call this feature auto-discovery or auto-registration.

I don't think people have been taking much advantage of this feature, though, for two reasons:

  1. We didn't mention it in the docs. (This would be the major reason.)
  2. There was no handy-dandy editor to quickly bash out a registration dictionary file.

Sure, we can all use Property List Editor, but that's not a tool honed to the purpose. You have to rifle through GrowlDefines.h to find all the different keys you can use in the dictionary (the docs only cover the most basic set).

Well, now there's a tool honed to the purpose.

Here's beta 1 of the Growl Registration Dictionary Editor. I invite you to try it. Obvious things worth doing:

  • Creating new reg dicts
  • Editing existing ones
  • Importing a reg dict from your saved tickets (great for porting your existing app to use auto-reg!)

Feedback will be appreciated. If you're subscribed to the Growl discussion list, you can send it there; if not, comments here are OK.

The source for the GRDE is in the Growl source repository.

If this announcement looks familiar to you, don't worry; you're not going crazy. I posted a similar message to the Growl discussion list, then decided to edit it a bit and post it here, since I know that at least several of you are Growl framework users who can put this app to good use.

An invalid property list

Monday, October 1st, 2007

If you work on an [added: a document-based] app that uses a plist-based format, you should test your plist-reading code to see how it handles an invalid (e.g., eaten by the user's dog) plist. Here's one:

<plist>
<qux></qux>
</plist>

The binary plist parser won't even touch it because it doesn't have the bplist header, the XML parser will choke on it because it contains an element that it doesn't recognize, and the OpenStep parser will choke on it because the first line doesn't end with a semicolon or backslash.

When you feed this to your app, your app should present an error message. Anything else is a bug. (UPDATE 2007-10-02: OK, maybe not. There's at least one circumstance where you can ignore it, as pointed out in the comments. I still think I'm right in most circumstances, just clearly not all.)

New utility: Make RAM Disk

Saturday, September 29th, 2007

Make RAM Disk is a simple app to create and mount a RAM disk on Mac OS X 10.4 or later. It encapsulates my previously-stated series of terminal commands into a tiny little app—just launch it and go.

I use my RAM disk for three things:

  • Download holding area. I have Mail, Safari, OmniWeb, and Adium all set to download files to the RAM disk. If I decide I don't want to keep the download, I just leave it there until shutdown (only works on my desktop Mac).
  • iShowU storage. I create a 1-GiB RAM Disk named “iShowU temp” which iShowU is set to use as its scratch disk. Using a RAM disk for this helps your recording frame rate.
  • Xcode build folder. On my laptop, I symlink the project's build folder over to the RAM disk (e.g., mkdir /Volumes/RAM\ Disk/Adium-build && ln -s /Volumes/RAM\ Disk/Adium-build build). This cuts out a lot of the disk grinding that happens during a build, which I believe helps my battery life.

Enjoy!

Appcasts on Google

Thursday, September 27th, 2007

If you publish an appcast, you should also publish a robots.txt file that tells search engines such as Google not index the appcast file. I came to this realization after searching for “appcast editor”: the first four hits, and several of the others, are all just appcasts.

There is no point to having these indexed by Google, so save the bandwidth and help people's search results—tell the search engines not to index them.

And I never did find an appcast editor. I guess I get to write it by hand in vim. (There's one in MacCode, but it's not working yet.)

UPDATE: I knew there was an appcast editor out there! It's Feeder, by Reinvented Software. Thanks to Andy Kim for pointing it out in the comments.

I do believe we have a record

Monday, September 24th, 2007
pngout \        %~/Projects/@otherpeoplesprojects/growl/trunk/Core/Resources(0)
> NotifyOSX.growlStyle/Contents/Resources/sidetitle.png 
 In:                             NotifyOSX.growlStyle/Contents/Resources/sidetitle.png
 In:   29644 bytes
Out:                             NotifyOSX.growlStyle/Contents/Resources/sidetitle.png
Out:     527 bytes               
Chg:  -29117 bytes (  1% of original)

zsh completion rocks

Monday, September 17th, 2007

Specifically, enable and configure zshcompsys. Here's what this will get you (6 seconds; 144 K; requires any version of QuickTime):

UPDATE 2007-09-18 11:54 PDT: More info in the comments.

Report-an-Apple-Bug Friday! 70

Friday, September 14th, 2007

This bug is Borken implementation of -setNilValueForKey: in Model Object Impl Guide. It was filed on 2007-09-14 at 12:24 PDT.

(more...)