 
     nonOverlappingElements
 
-  #
-  # Handles shift and esc keys. The other keys are passed to getMarkerMatcher().matchHintsByKey.
-  #
+  # Handles <Shift> and <Ctrl>.
   onKeyDownInMode: (hintMarkers, event) ->
     return if @delayMode or event.repeat
 
-    if ((event.keyCode == keyCodes.shiftKey or event.keyCode == keyCodes.ctrlKey) and
-        (@mode == OPEN_IN_CURRENT_TAB or
-         @mode == OPEN_WITH_QUEUE or
-         @mode == OPEN_IN_NEW_BG_TAB or
-         @mode == OPEN_IN_NEW_FG_TAB))
-      # Toggle whether to open the link in a new or current tab.
-      previousMode = @mode
-      keyCode = event.keyCode
-
-      switch keyCode
-        when keyCodes.shiftKey
-          @setOpenLinkMode(if @mode is OPEN_IN_CURRENT_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_CURRENT_TAB)
-        when keyCodes.ctrlKey
-          @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_NEW_FG_TAB)
-
-      handlerStack.push
-        keyup: (event) =>
-          if event.keyCode == keyCode
-            handlerStack.remove()
-            @setOpenLinkMode previousMode if @isActive
-          true
-
-    if (event.keyCode != keyCodes.shiftKey and event.keyCode != keyCodes.ctrlKey)
-      keyResult = @getMarkerMatcher().matchHintsByKey(hintMarkers, event)
-      linksMatched = keyResult.linksMatched
-      delay = keyResult.delay ? 0
-      if (linksMatched.length == 0)
+    if event.keyCode in [ keyCodes.shiftKey, keyCodes.ctrlKey ] and
+      @mode in [ OPEN_IN_CURRENT_TAB, OPEN_WITH_QUEUE, OPEN_IN_NEW_BG_TAB, OPEN_IN_NEW_FG_TAB ]
+        # Toggle whether to open the link in a new or current tab.
+        previousMode = @mode
+        keyCode = event.keyCode
+
+        switch keyCode
+          when keyCodes.shiftKey
+            @setOpenLinkMode(if @mode is OPEN_IN_CURRENT_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_CURRENT_TAB)
+          when keyCodes.ctrlKey
+            @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_NEW_FG_TAB)
+
+        handlerStack.push
+          keyup: (event) =>
+            if event.keyCode == keyCode
+              handlerStack.remove()
+              @setOpenLinkMode previousMode if @isActive
+
+    else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]
+      if @markerMatcher.popKeyChar()
+        @updateVisibleMarkers hintMarkers
+      else
         @deactivateMode()
-      else if (linksMatched.length == 1)
-        @activateLink(linksMatched[0], delay)
-      else
-        for marker in hintMarkers
-          @hideMarker(marker)
-        for matched in linksMatched
-          @showMarker(matched, @getMarkerMatcher().hintKeystrokeQueue.length)
-    false # We've handled this key, so prevent propagation.
+
+    else if @markerMatcher.activateOnEnter and event.keyCode == keyCodes.enter
+      # Activate the lowest-numbered link hint that matches the current state.
+      @updateVisibleMarkers hintMarkers, true
+
+    else
+      return
+
+    # We've handled the event, so suppress it.
+    DomUtils.suppressEvent event
+
+  # Handles normal input.
+  onKeyPressInMode: (hintMarkers, event) ->
+    return if @delayMode or event.repeat
+
+    keyChar = String.fromCharCode(event.charCode).toLowerCase()
+    if keyChar
+      @markerMatcher.pushKeyChar keyChar
+      @updateVisibleMarkers hintMarkers
+
+  updateVisibleMarkers: (hintMarkers, activateFirst = false) ->
+    keyResult = @markerMatcher.getMatchingHints hintMarkers
+    linksMatched = keyResult.linksMatched
+    if (linksMatched.length == 0)
+      @deactivateMode()
+    else if activateFirst
+      @activateLink(linksMatched[0], 0)
+    else if (linksMatched.length == 1)
+      @activateLink(linksMatched[0], keyResult.delay ? 0)
+    else
+      for marker in hintMarkers
+        @hideMarker(marker)
+      for matched in linksMatched
+        @showMarker(matched, @markerMatcher.hintKeystrokeQueue.length)
 
   #
   # When only one link hint remains, this function activates it in the appropriate way.

      result = result.concat(bucket)
    result

  matchHintsByKey: (hintMarkers, event) ->
    # If a shifted-character is typed, treat it as lowerase for the purposes of matching hints.
    keyChar = String.fromCharCode(event.charCode).toLowerCase()

    if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey)
      if (!@hintKeystrokeQueue.pop())
        return { linksMatched: [] }
    else if keyChar
      @hintKeystrokeQueue.push(keyChar)

    matchString = @hintKeystrokeQueue.join("")
    linksMatched = hintMarkers.filter((linkMarker) -> linkMarker.hintString.indexOf(matchString) == 0)
    { linksMatched: linksMatched }

  deactivate: -> @hintKeystrokeQueue = []

filterHints =
  hintKeystrokeQueue: []
  linkTextKeystrokeQueue: []
  labelMap: {}


LinkHints =
  hintMarkerContainingDiv: null
  # One of the enums listed at the top of this file.
  mode: undefined
  # Function that does the appropriate action on the selected link.
  linkActivator: undefined
  # While in delayMode, all keypresses have no effect.
  delayMode: false
  # Lock to ensure only one instance runs at a time.
  isActive: false
  # The link-hints "mode" (in the key-handler, indicator sense).
  hintMode: null
  # Call this function on exit (if defined).
  onExit: null

