Bill Bumgarner

2004-12-28

KQueue; nothing is ever as simple as assumed.

screenshot

Over vacation, I have recreationally hacked upon ReSTedit. In particular, I wanted to add two features that have been on the todo list since the project's inception.

First, the toolbar has always had a segmented control that indicates whether both the source and rendered views were visible or which one was visible in the case where the vertical split view was fully expanded or collapsed.

It was totally useless. The original intention was to allow the user to click on one of the segments to pop the split view to a particular position, as appropriate.

Unfortunately, NSSplitView does not have API to tightly control the position of the split. Rainer Brockerhoff wrote a replacement for NSplitView called RBSplitView that does provide programatic control over the split.

Once I figured out how to plug the views into RBSplitView, it all just works. The real challenge was mostly of my own creation. RBSplitView is really designed to work via instantiation from an IB palette. I chose to configure it from my code because I didn't want to have the palette around to edit the NIB and I eventually want to add some dynamic UI behavior.

Now, the reason why I wanted the above feature was for the second feature. Long ago, I added Uli's UKKQueue class to the project. The intention was to monitor any opened files and automatically re-render the ReStructured Text whenever a file is changed on disk.

I never got around to implementing the feature because I assumed it would be more challenging than it appeared and I didn't have the time to go off and figure out what the hell was going on if something break.

That particular assumption proved to be very accurate.

KQueue's work by monitoring a particular open file for various events like delete, rename, write, attributes changed, or the like.

My initial naive implementation optimistically assumed that I could monitor the file for WRITE events and respond by reverting the document.

That didn't work. As a matter of fact, it broke in different ways depending on which app the file was saved within. For emacs, you might see a delete event or a rename event depending on if a backup file is created or not. This also means that NSDocument will sometimes follow the file to the backup; that is, you'll end up editing 'foo.txt~' instead of 'foo.txt'. For TextEdit, it is a slightly different sequence of events.

All in all, it required quite a bit of head scratching and 'print' style debugging to figure out what the hell was really going on with each editor. I think I have a relatively complete solution now. Of course, then there was the little problem of leaking open file descriptors.

In the end, I watch for WRITE, DELETE, RENAME or EXTEND events. If any are received, I revert the document (and warn the user if there are unsaved changes that might be lost).

All of the code can be found in the ReSTedit SVN repository. REDocument.py contains all of the kqueue related code beyond the very simple bridge code found in ukkqueue.py. REWindowController.py contains all of the code related to the RBSplitView configuration and interaction.

Comment on this post [ so far] ... more like this: [Mac OS X, PyObjC] ... topic exchange: [Mac OS X, PyObjC]