   getHtml: ->
     @medium.value()
 
-  handleKeyup: (event) =>
-    selection = window.getSelection()
-    range     = selection.getRangeAt 0
-    textNode  = range.commonAncestorContainer
+  handleKeydown: (event) =>
+    callHandler = (handler) =>
+      selection   = window.getSelection()
+      range       = selection.getRangeAt 0
+      caretOffset = range.startOffset
+      textNode    = range.commonAncestorContainer
+
+      handler.apply this, [event, @elem, caretOffset, textNode]
 
     # TODO preventDefault on ctrl+S and cmd+S
     # TODO intelligently handle Up Arrow at the beginning of a section
     # TODO intelligently handle Down Arrow at the end of a section
+    switch event.keyCode
+      when 222 then callHandler @applySmartQuote
+      when 189 then callHandler @applySmartEmDash
 
-    # The user just typed something and has a collapsed selection.
-    if range.collapsed && @elem.contains textNode
-      # The character right before the caret
-      prevChar = textNode.textContent[range.endOffset - 1]
+  applySmartQuote: (event, elem, caretOffset, textNode) =>
+    event.preventDefault()
 
-      switch prevChar
-        when "\""
-          @applySmartQuote      selection
-        when "'"
-          @applySmartApostrophe selection
-        when "-"
-          # If the user typed --, convert to em dash
-          if prevChar == textNode.textContent[range.endOffset - 2]
-            @applySmartEmDash   selection
+    precededByWord = textNode.textContent[caretOffset - 1]?.match /\S/
 
-  applySmartQuote: (selection) =>
-  applySmartApostrophe: (selection) =>
-  applySmartEmDash: (selection) =>
+    char = if event.shiftKey
+      if precededByWord then "”" else "“"
+    else
+      if precededByWord then "’" else "‘"
+
+    @replaceWith textNode, caretOffset, caretOffset, char
+
+  applySmartEmDash: (event, elem, caretOffset, textNode) =>
+    # The user typed "--", which we will now convert to an em dash.
+    if "-" == textNode.textContent[caretOffset - 1]
+      event.preventDefault()
+
+      @replaceWith textNode, caretOffset - 1, caretOffset, emDash
+
+  replaceWith: (textNode, startOffset, endOffset, html, callback) =>
+    newRange  = document.createRange()
+    selection = window.getSelection()
+
+    newRange.setStart textNode, startOffset
+    newRange.setEnd   textNode, endOffset
+
+    selection.removeAllRanges()
+    selection.addRange newRange
+
+    @medium.insertHtml html, callback
 
   execCommand: (command, skipObserver) =>
     @runWithOptionalObserver skipObserver, =>

# 2. Writes to the element when requested, optionally while disabling the 
#    mutation observer to avoid spurious updates.
Medium = require "./medium.js"

module.exports = class Editor
  constructor: (@elem, @mutationObserverOptions, enableRichText, @onChange) ->

      console.error "Error setting up Medium:", err
      throw err

    @elem.addEventListener "keydown", @handleKeydown

    @enableMutationObserver()

