Setting up Xcode 4.3 (for MacVim, Homebrew and Haskell)

Xcode 4.3 was released recently and one of the changes it brought with it was that the /Developer folder now has moved into the Xcode app bundle. This has caused headaches for lots of developers and MacVim was not spared either. I recently did a clean install of Mac OS X Lion and Xcode 4.3 and thought I’d document my experiences in this post.

After installing Xcode 4.3 (my version says 4.3.1) you must manually go into the Xcode preferences, select the Downloads tab and install the Command Line Tools. Even after this step you will not be able to use automake and friends; these have to be installed manually.

My intention was to use Homebrew with my fresh install (I had never bothered with this before as my /usr/local was occupied by my stuff and Homebrew strongly advices against that). However, before Homebrew will work you have to tell Xcode where the Developer dir is, otherwise Xcode still thinks it should be at /Developer (this is after a clean install mind you, so I think this is a bug in Xcode 4.3). Open up Terminal and enter:

$ xcode-select -switch /Applications/Xcode.app/Contents/Developer

Now you can install autotools with Homebrew by typing

$ brew install autoconf automake

With this setup it is now possible to compile MacVim without any problems (actually, you don’t need autotools to build MacVim since it comes with a pre-generated configure script but I need autoconf to generate said script).

Lastly, it turns out Haskell was broken by Xcode 4.3 as well (cabal would complain about gcc not being found). To fix it, open up /usr/bin/ghc with a text editor and look for the line which says pgmgcc="/Developer/usr/bin/gcc" and change this to say

pgmgcc="/usr/bin/gcc"

Using gettext in a Cocoa application

Vim uses gettext (from libintl) to support localized messages like (I’m guessing) many *nix programs do. Mac OS X on the other hand uses bundles for localized messages so I had to struggle somewhat to get these two to cooperate when internationalizing MacVim. In this post I’ll describe how gettext and Cocoa decides which language to use for localized messages and how I managed to get both to choose the same language.

The “International” System Preference is used to control which language to use for localized messages. Unfortunately, Cocoa applications uses the “Language” tab setting whereas gettext uses the “Formats” tab. For example, if I choose English as my preferred language in the “Language” tab, but my “Formats” tab region set to “Sweden”, then gettext will use Swedish for messages (if available) but Cocoa will use English.

My preference was to get gettext to behave in the same manner as Cocoa instead of the other way around. Fortunately, this is made easy via the LANGUAGE environment variable. This is a colon separated list of preferred languages (e.g. sv:en) and it takes precedence over the LC_* and LANG environment variables when gettext chooses which language to use for localized messages. The list of user-preferred languages (as set in the “Language” tab) can be accessed via +[NSLocale preferredLanguages]. The following Objective-C code sets LANGUAGE to match the user’s choice:

NSArray *languages = [NSLocale preferredLanguages];
if (languages && [languages count] > 0) {
    int i, count = [languages count];
    for (i = 0; i < count; ++i) {
        if ([[languages objectAtIndex:i]
                isEqualToString:@"en"]) {
            count = i+1;
            break;
        }
    }
    NSRange r = { 0, count };
    NSString *s = [[languages subarrayWithRange:r]
            componentsJoinedByString:@":"];
    setenv("LANGUAGE", [s UTF8String], 0);
}

One note about this code: I’ve only included fallback languages up to (and including) “en” (English) since Vim has strings in English in the actual code and hence there is no .mo file for English translations. If I did not disregard all languages after “en” then English would never be used. Also note that the last parameter in the call to setenv() is 0 so that if the user has already set LANGUAGE then we do not override the user’s choice.

Coercing the Cocoa text system

I just spent the last couple of days refactoring keyboard input handling in MacVim and thought I’d write down some of my findings here for posterity. I’ve read Apple’s documentation on the Cocoa text system over and over again, but it assumes that you are writing a new Cocoa application from scratch and it doesn’t really cover the case when you are porting an app which already has its own way of dealing with keyboard input. If you are in the latter situation, these notes may be of use to you.

My expectations in MacVim are these: I want to enable the use of different input sources (different keyboard layouts as well as more complex input sources such as Kotoeri) but at the same time I need to pass raw key events (which key was pressed and which modifiers were held?) on to Vim since it does its own processing.

Keyboard input arrives in an NSView derived subclass as follows (by “Cmd keys” I mean keys pressed at the same time as Cmd is held and similarly for Ctrl/Alt/Shift):

  1. Cmd and Ctrl keys are sent to performKeyEquivalent: before being passed on to the app menus for key equivalent handling. Note: on Tiger Ctrl keys go directly to keyDown: and (more importantly) if you don’t handle a Cmd key here it will never reach keyDown:. In Leopard Apple fixed this problem: unhandled Cmd key presses (i.e. ones that did not have a menu item bound to them) are sent to keyDown:.The way MacVim handles Cmd key presses on Tiger is to call [[NSApp mainMenu] performKeyEquivalent:event] inside performKeyEquivalent: and if that returns NO send the Cmd key on to Vim and return YES. This way it is possible to bind menu items to key equivalents and let Vim deal with unbound Cmd keys.
  2. Next keyDown: is called. Ideally at this stage I’d like to inspect the NSEvent and pass it on to Vim, but doing so would disable input sources like Kotoeri. To give these a chance to respond to the key event you must call interpretKeyEvents:. When this is done, one of the following will happen:
    • insertText: is called with text to insert (in the form of a NSString or a NSAttributedString)
    • doCommandBySelector: is called with a selector whose name indicates what should happen (e.g. insertNewline:) but the selector name can also be noop: (which is meaningless)
    • the current input source swallows the event and tells us to add marked text (e.g. this happens when you press Alt+i on a US keyboard)
    • the input manager swallows the event and nothing happens (e.g. you press Cmd+Alt and some key which was not bound to a menu item)

If this looks complicated, well, that’s because it is! Lets discuss in more detail the things that can happen as a result of calling interpretKeyEvents:.

Normal text

“Normally” insertText: is called with the text to insert, e.g. when you hit a key by itself or with Shift held. This is also the case with most Alt keys (on a US keyboard layout) except for some “dead keys” such as Alt+i and Alt+e. Be careful with the parameter to insertText: — it may be either a NSString or a NSAttributedString. In MacVim I explicitly check for the latter case and extract the string from it (since I can’t support the attributes anyway) as follows:

- (void)insertText:(id)string
{
    if ([string isKindOfClass:[NSAttributedString class]])
        string = [string string];

Note that insertText: may be called with more than one character, e.g. when inserting previously marked text. Typically this happens when you use complex input sources such as Kotoeri. Also, insertText: may be called without any keys being pressed at all. For example, the “Special Characters” palette may be used to insert text (this palette can usually be found on the “Edit” menu) so don’t rely on the current event being a key event inside insertText:.

Key bindings

Keys that have a key binding on the other hand will cause doCommandBySelector: to be called, e.g. Ctrl+[ is by default bound to cancelOperation:. In this situation it is a bit tricky to figure out what to do. One possibility is to do nothing and then back in keyDown: look at the NSEvent object to figure out what to do (e.g. by calling characters on the event) and this may work in most cases. However, there are several cases I know of where it does not work so well.

For example, if you press "Alt+i, Return" then the first press (Alt+i) causes marked text to be added, and the second (Return) will first cause the input manager to call insertText: with the marked text (^) and then doCommandBySelector: is called with the selector insertNewline:. If you inspect the [NSEvent characters] in keyDown: for the event generated by the Return press, it will return ^\x0d and not \x0d as you would expect (i.e. the event contains the result of the Alt+i press as well!). In MacVim I check for certain selectors (e.g. insertNewline:) in doCommandBySelector: and pass an appropriate key on to Vim, but most selectors I ignore and deal with it in keyDown: instead.

Swallowed key events

The problem with the input manager swallowing events is hard to deal with. It can be solved by adding appropriate key bindings but currently there is no public API to modify the key bindings at run time.

For example, Cmd+Alt keys will be blocked, but if you add an entry to the key bindings dictionary with the key "@~" and value noop: (@=Cmd, ~=Alt) then the input manager will pass any Cmd+Alt presses on to doCommandBySelector: with selector noop:. One way to deal with this via Private APIs is to set your own key binding dictionary when initializing your app and make sure your own dictionary contains entries such as "@~" to let key presses through. This is the API you can use to do so:

// Private AppKit API (from class-dump)
@interface NSKeyBindingManager : NSObject
+ (id)sharedKeyBindingManager;
- (id)dictionary;
- (void)setDictionary:(id)arg1;
@end

And here is how to set your own dictionary (assuming dict has been populated with your own key bindings already):

NSKeyBindingManager *mgr =
    [NSKeyBindingManager sharedKeyBindingManager];
[mgr setDictionary:dict];

I have yet to find any problems caused by this, but try it at your own risk. One thing to note is that your custom key bindings dictionary should contain most (if not all) of the bindings found in the system default key bindings dictionary

/System/Library/Frameworks/AppKit.framework
            /Resources/StandardKeyBinding.dict

else keyboard navigation may not work in dialogs since they rely on these standard bindings (e.g. forget to bind Return to insertNewline: and you won't be able to use Return to dismiss dialogs).

The unexpected Ctrl+q

Another gotcha I came across in my investigations was that Ctrl+q always seemed to be swallowed by the input manager and then the next keystroke would get mangled in one way or another. The problem turned out to be a User Default called NSQuotedKeystrokeBinding. By default this is set to "^q" (Ctrl+q) and is used to literally enter keys that would otherwise be interpreted by the input manager. Hence, if you ever want to receive any Ctrl+q presses this needs to be disabled, which fortunately is easy using a little bit of Core Foundation:

CFPreferencesSetAppValue(
        CFSTR("NSQuotedKeystrokeBinding"),
        CFSTR(""),
        kCFPreferencesCurrentApplication);

This should be called in the initialization of your application.

Marked text

Apple is a bit vague on how to support marked text handling in custom views. Basically, they say you should implement the NSTextInput protocol but then they don't exactly explain how this is done.

One method that seems to work well is to keep a NSString and a NSRange in your custom view and whenever setMarkedText:selectedRange: is called you update your marked text and range variables. Occasionally the input manager will ask if there is any marked text and what the selected range is by calling markedText and markedRange. When the user finishes entering marked text (e.g. by hitting Enter in Kotoeri manager), the input manager calls insertText: to let you know that it is finished with the current marked text entry. As a result you should always clear you local marked text and range variables. Note that the input manager almost never explicitly calls unmarkText to unmark the text, which is why you need to do the unmarking manually in insertText:.

Command keys

When a Cmd key press reaches keyDown: I ideally would like to know which key was pressed and which modifiers were held but there is a problem here in that Cocoa sometimes will add the modifiers into the key and sometimes not. This turns into a game as to whether to extract characters or charactersIgnoringModifiers from the NSEvent and trying to guess which of the flags in modifierFlags have already been included in the event.

This is already turning into one massive post so let me just summarize the heuristic I finally settled on:

  • If Shift and/or Alt is held, then use charactersIgnoringModifiers, otherwise use characters
  • The Shift flag is always included in the key so clear it from modifierFlags. The Alt flag should only be cleared if the Ctrl flag is set as well.

Note that I could not find any (reasonably easy) way of extracting which unmodified key on the physical keyboard was pressed. Even though the method is called charactersIgnoringModifiers it will some times include the modifier flags (e.g. Shift, Alt) and sometimes it won't.

MacVim Services

Many people probably never use the “Services” item that appears on the application menu in every Mac OS X application. At least I never used to, and for two reasons: I didn’t even know it existed for a long time and then when I did learn about it I could never be bothered navigating through all those submenus. I’ve since found a good way to actually use the Services menu, namely via Mac OS X’s ability to customize keyboard shortcuts for any menu item in any application.

Consider the MacVim service “Open Document Containing Selection” (phew). With it you can select some text in (almost) any application, run the service and a new MacVim window will open up with the selected text. Using the Services menu to do this would take a lot more time then to simply ⌘C, switch to MacVim, ⌘N, then ⌘V but by assigning your own keyboard shortcut to this service it actually becomes useful.

Here’s how to assign ⌃⌥⌘V to this service (yes, that’s a lot of keys, but at least it’s unlikely to be assigned to some other menu and it is not too awkward to type):

  1. Click the Apple Menu
  2. Choose “System Preferences…”
  3. Click “Keyboard & Mouse”
  4. Click the “Keyboard Shortcuts” tab
  5. Click “+” button in the bottom left corner
  6. In the sheet that pops down, choose “All Applications” from the drop down menu
  7. In the text box next to “Menu Title” enter “New Document Containing Selection” (double-check for typos or the shortcut won’t work)
  8. Click the edit box next to “Keyboard Shortcut” and hold down Control, Option, Command and v at the same time, then click the “Add” button

Now try it out! For example open Safari, select some text, and hit ⌃⌥⌘V. If it does not work then you have most likely misspelled the menu item in step 7, or you have chosen a shortcut that is already in use (⌃⌥⌘V worked in Safari for me). Finding a shortcut that works across all applications can be kind of frustrating and sometimes you have to restart an application before it will notice a change to the shortcut.

Another tip is to assign a shortcut to the “New Document Here” service for Finder only (choose “Finder” instead of “All Applications” from the drop down menu in step 6 and enter “New Document Here” in step 7). Using that shortcut will open a new MacVim window and the current directory will be set to whatever folder you are currently browsing in the Finder. I often use this to create a new document on the Desktop by clicking the Desktop, hitting the shortcut, enter some text, then hit ⌘S and enter a file name. Give it a try.