I first implemented smart quotes in the desk accessory miniWRITER, and then in Acta. I don’t know the exact date, but the earliest relevant change comment in miniWRITER is a bug fix in version 1.05, on 24 August 1986. So smart quotes are probably almost 20 years old.
I originally offered the algorithm to anyone who asked, provided they sent me a copy of the application it appeared in. I know PageMaker used it, as did WriteNow. Other applications have reverse-engineered the process. Unfortunately, they seldom offer a way to enter a straight quote (or inch mark, ").
unichar gLeftApostrophe = 0x2018; unichar gRightApostrophe = 0x2019; unichar gLeftQuote = 0x201C; unichar gRightQuote = 0x201D; - (void) keyDown: (NSEvent*) anEvent { // Don't worry about having to allocate an NSString; [NSTextView keyDown:] will do so anyway, // and it's apparently lazily instantiated by NSEvent. NSString* unmodifiedKeys = [anEvent charactersIgnoringModifiers]; NSString* newKeys; // Grab the first character, so we don't have to send messages to test for all the possibilities // NOTE: In some cases, we get more than one character at once, if someone is banging on // the keys or something. It might be better to iterate through unmodifiedKeys. unichar theChar = [unmodifiedKeys length] > 0 ? [unmodifiedKeys characterAtIndex: 0] : 0; unichar prevChar; if (theChar == '"' || theChar == '\'') { // Possible smart quote/apostrophe if (![[NSUserDefaults standardUserDefaults] boolForKey: @"smartQuotes"]) { [super keyDown: anEvent]; return; } if ([anEvent modifierFlags] & NSControlKeyMask) { // Override smart quotes with ctrl key; we will need to strip the modifier newKeys = [NSString stringWithCharacters: &theChar length: 1]; } else { NSCharacterSet* startSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; NSRange selection = [self selectedRange]; if (selection.location == 0) prevChar = 0; else prevChar = [[self string] characterAtIndex: selection.location - 1]; if (prevChar == 0 || // Beginning of text prevChar == '(' || prevChar == '[' || prevChar == '{' || // Left thingies prevChar == '<' || prevchar == 0x00AB || // More left thingies prevChar == 0x3008 || prevChar == 0x300A || // Even more left thingies (we could add more Unicode) (prevChar == gLeftQuote && theChar == '\'') || // Nest apostrophe inside quote (prevChar == gLeftApostrophe && theChar == '"') || // Alternate nesting [startSet characterIsMember: prevChar]) // Beginning of word/line newKeys = [NSString stringWithCharacters: theChar == '"' ? &gLeftQuote : &gLeftApostrophe length: 1]; else newKeys = [NSString stringWithCharacters: theChar == '"' ? &gRightQuote : &gRightApostrophe length: 1]; } NSEvent *newEvent = [NSEvent keyEventWithType: [anEvent type] location: [anEvent locationInWindow] modifierFlags: 0 timestamp:1 windowNumber:[[NSApp mainWindow] windowNumber] context:[NSGraphicsContext currentContext] characters:newKeys charactersIgnoringModifiers:newKeys isARepeat:NO keyCode: 0]; [super keyDown: newEvent]; } else { [super keyDown: anEvent]; } } // keyDown:Thanks to Justin Bur for pointing out a bug (fixed above): “Many non-U.S. keyboards have floating accent keys, available without any modifiers, which generate a keyDown event with no characters.”