Smart Quotes (" ' → “ ” ‘ ’)

Overview

“Smart Quotes” is the automatic replacement of the correct typographic quote character (‘ or ’ and “ or ”) as you type (' and "). It does not refer to the curved quotes themselves.

History

In the early days of Macintosh, you used to have to remember some arcane keyboard combinations to enter curved quotes. One day, while driving from Santa Barbara to a user group in southern California, I realized that this could be done automatically.

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, ").

Summary

As the user types, the characters typed are automatically replaced, according to the context of the insertion point, before being inserted into the text. A quote is turned into its left equivalent if it is at the beginning of the text, or if it follows a space, tab, return, or left punctuation (‘(,’ ‘[,’ ‘{,’ or ‘<’). A quote is also considered an opening quote if it follows an opening quote of the opposite type (as in: “‘sorry’ is all you have to say?” she asked).

Cocoa Implementation

Subclass NSTextView and override keyDown:.
 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.”
Copyright ©2006. Last updated 18 Nov 22 drd

David Dunham Page