 
     {linkText, showLinkText}
 
-# TODO(smblott) Again, this is temporary.  We need to move the code above out of the "old" link-hints class.
-class LinkHintsMode extends LinkHintsModeBase
-  constructor: (args...) -> super args...
-
-  # Handles <Shift> and <Ctrl>.
-  onKeyDownInMode: (hintMarkers, event) ->
-    return if event.repeat
-    @keydownKeyChar = KeyboardUtils.getKeyChar(event).toLowerCase()
-
-    previousTabCount = @tabCount
-    @tabCount = 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 ]
-        @tabCount = previousTabCount
-        # 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)
-
-        handlerId = handlerStack.push
-          keyup: (event) =>
-            if event.keyCode == keyCode
-              handlerStack.remove()
-              @setOpenLinkMode previousMode
-            true # Continue bubbling the event.
-
-        # For some (unknown) reason, we don't always receive the keyup event needed to remove this handler.
-        # Therefore, we ensure that it's always removed when hint mode exits.  See #1911 and #1926.
-        @hintMode.onExit -> handlerStack.remove handlerId
-
-    else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]
-      if @markerMatcher.popKeyChar()
-        @updateVisibleMarkers hintMarkers
-      else
-        # Exit via @hintMode.exit(), so that the LinkHints.activate() "onExit" callback sees the key event and
-        # knows not to restart hints mode.
-        @hintMode.exit event
-
-    else if event.keyCode == keyCodes.enter
-      # Activate the active hint, if there is one.  Only FilterHints uses an active hint.
-      HintCoordinator.sendMessage "activateActiveHintMarker" if @markerMatcher.activeHintMarker
-
-    else if event.keyCode == keyCodes.tab
-      @tabCount = previousTabCount + (if event.shiftKey then -1 else 1)
-      @updateVisibleMarkers hintMarkers, @tabCount
-
-    else
-      return
-
-    # We've handled the event, so suppress it.
-    DomUtils.suppressEvent event
-
-  # Handles normal input.
-  onKeyPressInMode: (hintMarkers, event) ->
-    return if event.repeat
-
-    keyChar = String.fromCharCode(event.charCode).toLowerCase()
-    if keyChar
-      @markerMatcher.pushKeyChar keyChar, @keydownKeyChar
-      @updateVisibleMarkers hintMarkers
-
-    # We've handled the event, so suppress it.
-    DomUtils.suppressEvent event
-
-  updateVisibleMarkers: (hintMarkers, tabCount = 0) ->
-    {hintKeystrokeQueue, linkTextKeystrokeQueue} = @markerMatcher
-    HintCoordinator.sendMessage "updateKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}
-
-  updateKeyState: (hintMarkers, {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) ->
-    extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue}
-
-    {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints hintMarkers, tabCount
-    if linksMatched.length == 0
-      @deactivateMode()
-    else if linksMatched.length == 1
-      @activateLink linksMatched[0], userMightOverType ? false
-    else
-      @hideMarker marker for marker in hintMarkers
-      @showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched
-
-  # When only one hint remains, activate it in the appropriate way.  The current frame may or may not contain
-  # the matched link, and may or may not have the focus.  The resulting four cases are accounted for here by
-  # selectively pushing the appropriate HintCoordinator.onExit handlers.
-  activateLink: (linkMatched, userMightOverType=false) ->
-    @removeHintMarkers()
-    clickEl = HintCoordinator.getLocalHintMarker(linkMatched.hintDescriptor)?.element
-
-    if clickEl?
-      HintCoordinator.onExit.push =>
-        if clickEl == document.body
-          Utils.nextTick -> focusThisFrame highlight: true
-        else if DomUtils.isSelectable clickEl
-          window.focus()
-          DomUtils.simulateSelect clickEl
-        else
-          clickActivator = (modifiers) -> (link) -> DomUtils.simulateClick link, modifiers
-          linkActivator = @mode.linkActivator ? clickActivator @mode.clickModifiers
-          # TODO: Are there any other input elements which should not receive focus?
-          if clickEl.nodeName.toLowerCase() == "input" and clickEl.type not in ["button", "submit"]
-            clickEl.focus()
-          linkActivator clickEl
-
-    installKeyBoardBlocker = (startKeyboardBlocker) ->
-      if linkMatched.hintDescriptor.frameId == frameId
-        flashEl = DomUtils.addFlashRect linkMatched.hintDescriptor.rect
-        HintCoordinator.onExit.push -> DomUtils.removeElement flashEl
-
-      if document.hasFocus()
-        startKeyboardBlocker -> HintCoordinator.sendMessage "exit"
-
-    HintCoordinator.onExit.push => @deactivateMode()
-    # If we're using a keyboard blocker, then the frame with the focus sends the "exit" message, otherwise the
-    # frame containing the matched link does.
-    if userMightOverType and Settings.get "waitForEnterForFilteredHints"
-      installKeyBoardBlocker (callback) -> new WaitForEnter callback
-    else if userMightOverType
-      installKeyBoardBlocker (callback) -> new TypingProtector 200, callback
-    else if linkMatched.hintDescriptor.frameId == frameId
-      DomUtils.flashRect linkMatched.hintDescriptor.rect
-      HintCoordinator.sendMessage "exit"
-
-  #
-  # Shows the marker, highlighting matchingCharCount characters.
-  #
-  showMarker: (linkMarker, matchingCharCount) ->
-    return unless linkMarker.hintDescriptor.frameId == frameId
-    linkMarker.style.display = ""
-    for j in [0...linkMarker.childNodes.length]
-      if (j < matchingCharCount)
-        linkMarker.childNodes[j].classList.add("matchingCharacter")
-      else
-        linkMarker.childNodes[j].classList.remove("matchingCharacter")
-
-  hideMarker: (linkMarker) -> linkMarker.style.display = "none"
-
-  deactivateMode: ->
-    @removeHintMarkers()
-    @hintMode?.exit()
-
-  removeHintMarkers: ->
-    DomUtils.removeElement @hintMarkerContainingDiv if @hintMarkerContainingDiv
-    @hintMarkerContainingDiv = null
-
-# Use characters for hints, and do not filter links by their text.
-class AlphabetHints
-  constructor: ->
-    @linkHintCharacters = Settings.get "linkHintCharacters"
-    # We use the keyChar from keydown if the link-hint characters are all "a-z0-9".  This is the default
-    # settings value, and preserves the legacy behavior (which always used keydown) for users which are
-    # familiar with that behavior.  Otherwise, we use keyChar from keypress, which admits non-Latin
-    # characters. See #1722.
-    @useKeydown = /^[a-z0-9]*$/.test @linkHintCharacters
-    @hintKeystrokeQueue = []
-
-  fillInMarkers: (hintMarkers) ->
-    hintStrings = @hintStrings(hintMarkers.length)
-    for marker, idx in hintMarkers
-      marker.hintString = hintStrings[idx]
-      marker.innerHTML = spanWrap(marker.hintString.toUpperCase())
-
-    hintMarkers
-
-  #
-  # Returns a list of hint strings which will uniquely identify the given number of links. The hint strings
-  # may be of different lengths.
-  #
-  hintStrings: (linkCount) ->
-    hints = [""]
-    offset = 0
-    while hints.length - offset < linkCount or hints.length == 1
-      hint = hints[offset++]
-      hints.push ch + hint for ch in @linkHintCharacters
-    hints = hints[offset...offset+linkCount]
-
-    # Shuffle the hints so that they're scattered; hints starting with the same character and short hints are
-    # spread evenly throughout the array.
-    return hints.sort().map (str) -> str.reverse()
-
-  getMatchingHints: (hintMarkers) ->
-    matchString = @hintKeystrokeQueue.join ""
-    linksMatched: hintMarkers.filter (linkMarker) -> linkMarker.hintString.startsWith matchString
-
-  pushKeyChar: (keyChar, keydownKeyChar) ->
-    @hintKeystrokeQueue.push (if @useKeydown then keydownKeyChar else keyChar)
-  popKeyChar: -> @hintKeystrokeQueue.pop()
-
-# Use numbers (usually) for hints, and also filter links by their text.
-class FilterHints
-  constructor: ->
-    @linkHintNumbers = Settings.get "linkHintNumbers"
-    @hintKeystrokeQueue = []
-    @linkTextKeystrokeQueue = []
-    @activeHintMarker = null
-    # The regexp for splitting typed text and link texts.  We split on sequences of non-word characters and
-    # link-hint numbers.
-    @splitRegexp = new RegExp "[\\W#{Utils.escapeRegexSpecialCharacters @linkHintNumbers}]+"
-
-  generateHintString: (linkHintNumber) ->
-    base = @linkHintNumbers.length
-    hint = []
-    while 0 < linkHintNumber
-      hint.push @linkHintNumbers[Math.floor linkHintNumber % base]
-      linkHintNumber = Math.floor linkHintNumber / base
-    hint.reverse().join ""
-
-  renderMarker: (marker) ->
-    marker.innerHTML = spanWrap(marker.hintString +
-        (if marker.showLinkText then ": " + marker.linkText else ""))
-
-  fillInMarkers: (hintMarkers) ->
-    @renderMarker marker for marker in hintMarkers
-
-    # We use @filterLinkHints() here (although we know that all of the hints will match) to fill in the hint
-    # strings.  This ensures that we always get hint strings in the same order.
-    @filterLinkHints hintMarkers
-
-  getMatchingHints: (hintMarkers, tabCount = 0) ->
-    # At this point, linkTextKeystrokeQueue and hintKeystrokeQueue have been updated to reflect the latest
-    # input. use them to filter the link hints accordingly.
-    matchString = @hintKeystrokeQueue.join ""
-    linksMatched = @filterLinkHints hintMarkers
-    linksMatched = linksMatched.filter (linkMarker) -> linkMarker.hintString.startsWith matchString
-
-    # Visually highlight of the active hint (that is, the one that will be activated if the user
-    # types <Enter>).
-    tabCount = ((linksMatched.length * Math.abs tabCount) + tabCount) % linksMatched.length
-    @activeHintMarker?.classList.remove "vimiumActiveHintMarker"
-    @activeHintMarker = linksMatched[tabCount]
-    @activeHintMarker?.classList.add "vimiumActiveHintMarker"
-
-    linksMatched: linksMatched
-    userMightOverType: @hintKeystrokeQueue.length == 0 and 0 < @linkTextKeystrokeQueue.length
-
-  pushKeyChar: (keyChar, keydownKeyChar) ->
-    # For filtered hints, we *always* use the keyChar value from keypress, because there is no obvious and
-    # easy-to-understand meaning for choosing one of keyChar or keydownKeyChar (as there is for alphabet
-    # hints).
-    if 0 <= @linkHintNumbers.indexOf keyChar
-      @hintKeystrokeQueue.push keyChar
-    else
-      # Since we might renumber the hints, we should reset the current hintKeyStrokeQueue.
-      @hintKeystrokeQueue = []
-      @linkTextKeystrokeQueue.push keyChar
-
-  popKeyChar: ->
-    @hintKeystrokeQueue.pop() or @linkTextKeystrokeQueue.pop()
-
-  # Filter link hints by search string, renumbering the hints as necessary.
-  filterLinkHints: (hintMarkers) ->
-    linkSearchString = @linkTextKeystrokeQueue.join("").trim().toLowerCase()
-    do (scoreFunction = @scoreLinkHint linkSearchString) ->
-      linkMarker.score = scoreFunction linkMarker for linkMarker in hintMarkers
-    hintMarkers = hintMarkers[..].sort (a,b) ->
-      if b.score == a.score then b.stableSortCount - a.stableSortCount else b.score - a.score
-
-    linkHintNumber = 1
-    for linkMarker in hintMarkers
-      continue unless 0 < linkMarker.score
-      linkMarker.hintString = @generateHintString linkHintNumber++
-      @renderMarker linkMarker
-      linkMarker
-
-  # Assign a score to a filter match (higher is better).  We assign a higher score for matches at the start of
-  # a word, and a considerably higher score still for matches which are whole words.
-  scoreLinkHint: (linkSearchString) ->
-    searchWords = linkSearchString.trim().split @splitRegexp
-    (linkMarker) =>
-      text = linkMarker.linkText.trim()
-      linkWords = linkMarker.linkWords ?= text.toLowerCase().split @splitRegexp
-
-      searchWordScores =
-        for searchWord in searchWords
-          linkWordScores =
-            for linkWord, idx in linkWords
-              if linkWord == searchWord
-                if idx == 0 then 8 else 6
-              else if linkWord.startsWith searchWord
-                if idx == 0 then 4 else 2
-              else if 0 <= linkWord.indexOf searchWord
-                1
-              else
-                0
-          Math.max linkWordScores...
-
-      if 0 in searchWordScores
-        0
-      else
-        addFunc = (a,b) -> a + b
-        score = searchWordScores.reduce addFunc, 0
-        # Prefer matches in shorter texts.  To keep things balanced for links without any text, we just weight
-        # them as if their length was 50.
-        score / Math.log 1 + (text.length || 50)
-
-#
-# Make each hint character a span, so that we can highlight the typed characters as you type them.
-#
-spanWrap = (hintString) ->
-  innerHTML = []
-  for char in hintString
-    innerHTML.push("<span class='vimiumReset'>" + char + "</span>")
-  innerHTML.join("")
-
 # Suppress all keyboard events until the user stops typing for sufficiently long.
 class TypingProtector extends Mode
   constructor: (delay, callback) ->

  activateModeToOpenIncognito: (count) -> @activateMode count, OPEN_INCOGNITO
  activateModeToDownloadLink: (count) -> @activateMode count, DOWNLOAD_LINK_URL

class LinkHintsModeBase # This is temporary, because the "visible hints" code is embedded in the hints class.
  hintMarkerContainingDiv: null
  # One of the enums listed at the top of this file.
  mode: undefined


      marker

  # Handles <Shift> and <Ctrl>.
  onKeyDownInMode: (hintMarkers, event) ->
    return if event.repeat
    @keydownKeyChar = KeyboardUtils.getKeyChar(event).toLowerCase()

    previousTabCount = @tabCount
    @tabCount = 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 ]
        @tabCount = previousTabCount
        # 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)

        handlerId = handlerStack.push
          keyup: (event) =>
            if event.keyCode == keyCode
              handlerStack.remove()
              @setOpenLinkMode previousMode
            true # Continue bubbling the event.

        # For some (unknown) reason, we don't always receive the keyup event needed to remove this handler.
        # Therefore, we ensure that it's always removed when hint mode exits.  See #1911 and #1926.
        @hintMode.onExit -> handlerStack.remove handlerId

    else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]
      if @markerMatcher.popKeyChar()
        @updateVisibleMarkers hintMarkers
      else
        # Exit via @hintMode.exit(), so that the LinkHints.activate() "onExit" callback sees the key event and
        # knows not to restart hints mode.
        @hintMode.exit event

    else if event.keyCode == keyCodes.enter
      # Activate the active hint, if there is one.  Only FilterHints uses an active hint.
      HintCoordinator.sendMessage "activateActiveHintMarker" if @markerMatcher.activeHintMarker

    else if event.keyCode == keyCodes.tab
      @tabCount = previousTabCount + (if event.shiftKey then -1 else 1)
      @updateVisibleMarkers hintMarkers, @tabCount

    else
      return

    # We've handled the event, so suppress it.
    DomUtils.suppressEvent event

  # Handles normal input.
  onKeyPressInMode: (hintMarkers, event) ->
    return if event.repeat

    keyChar = String.fromCharCode(event.charCode).toLowerCase()
    if keyChar
      @markerMatcher.pushKeyChar keyChar, @keydownKeyChar
      @updateVisibleMarkers hintMarkers

    # We've handled the event, so suppress it.
    DomUtils.suppressEvent event

  updateVisibleMarkers: (hintMarkers, tabCount = 0) ->
    {hintKeystrokeQueue, linkTextKeystrokeQueue} = @markerMatcher
    HintCoordinator.sendMessage "updateKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}

  updateKeyState: (hintMarkers, {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) ->
    extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue}

    {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints hintMarkers, tabCount
    if linksMatched.length == 0
      @deactivateMode()
    else if linksMatched.length == 1
      @activateLink linksMatched[0], userMightOverType ? false
    else
      @hideMarker marker for marker in hintMarkers
      @showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched

  # When only one hint remains, activate it in the appropriate way.  The current frame may or may not contain
  # the matched link, and may or may not have the focus.  The resulting four cases are accounted for here by
  # selectively pushing the appropriate HintCoordinator.onExit handlers.
  activateLink: (linkMatched, userMightOverType=false) ->
    @removeHintMarkers()
    clickEl = HintCoordinator.getLocalHintMarker(linkMatched.hintDescriptor)?.element

    if clickEl?
      HintCoordinator.onExit.push =>
        if clickEl == document.body
          Utils.nextTick -> focusThisFrame highlight: true
        else if DomUtils.isSelectable clickEl
          window.focus()
          DomUtils.simulateSelect clickEl
        else
          clickActivator = (modifiers) -> (link) -> DomUtils.simulateClick link, modifiers
          linkActivator = @mode.linkActivator ? clickActivator @mode.clickModifiers
          # TODO: Are there any other input elements which should not receive focus?
          if clickEl.nodeName.toLowerCase() == "input" and clickEl.type not in ["button", "submit"]
            clickEl.focus()
          linkActivator clickEl

    installKeyBoardBlocker = (startKeyboardBlocker) ->
      if linkMatched.hintDescriptor.frameId == frameId
        flashEl = DomUtils.addFlashRect linkMatched.hintDescriptor.rect
        HintCoordinator.onExit.push -> DomUtils.removeElement flashEl

      if document.hasFocus()
        startKeyboardBlocker -> HintCoordinator.sendMessage "exit"

    HintCoordinator.onExit.push => @deactivateMode()
    # If we're using a keyboard blocker, then the frame with the focus sends the "exit" message, otherwise the
    # frame containing the matched link does.
    if userMightOverType and Settings.get "waitForEnterForFilteredHints"
      installKeyBoardBlocker (callback) -> new WaitForEnter callback
    else if userMightOverType
      installKeyBoardBlocker (callback) -> new TypingProtector 200, callback
    else if linkMatched.hintDescriptor.frameId == frameId
      DomUtils.flashRect linkMatched.hintDescriptor.rect
      HintCoordinator.sendMessage "exit"

  #
  # Shows the marker, highlighting matchingCharCount characters.
  #
  showMarker: (linkMarker, matchingCharCount) ->
    return unless linkMarker.hintDescriptor.frameId == frameId
    linkMarker.style.display = ""
    for j in [0...linkMarker.childNodes.length]
      if (j < matchingCharCount)
        linkMarker.childNodes[j].classList.add("matchingCharacter")
      else
        linkMarker.childNodes[j].classList.remove("matchingCharacter")

  hideMarker: (linkMarker) -> linkMarker.style.display = "none"

  deactivateMode: ->
    @removeHintMarkers()
    @hintMode?.exit()

  removeHintMarkers: ->
    DomUtils.removeElement @hintMarkerContainingDiv if @hintMarkerContainingDiv
    @hintMarkerContainingDiv = null

# Use characters for hints, and do not filter links by their text.
class AlphabetHints
  constructor: ->
    @linkHintCharacters = Settings.get "linkHintCharacters"
    # We use the keyChar from keydown if the link-hint characters are all "a-z0-9".  This is the default
    # settings value, and preserves the legacy behavior (which always used keydown) for users which are
    # familiar with that behavior.  Otherwise, we use keyChar from keypress, which admits non-Latin
    # characters. See #1722.
    @useKeydown = /^[a-z0-9]*$/.test @linkHintCharacters
    @hintKeystrokeQueue = []

  fillInMarkers: (hintMarkers) ->
    hintStrings = @hintStrings(hintMarkers.length)
    for marker, idx in hintMarkers
      marker.hintString = hintStrings[idx]
      marker.innerHTML = spanWrap(marker.hintString.toUpperCase())

    hintMarkers

  #
  # Returns a list of hint strings which will uniquely identify the given number of links. The hint strings
  # may be of different lengths.
  #
  hintStrings: (linkCount) ->
    hints = [""]
    offset = 0
    while hints.length - offset < linkCount or hints.length == 1
      hint = hints[offset++]
      hints.push ch + hint for ch in @linkHintCharacters
    hints = hints[offset...offset+linkCount]

    # Shuffle the hints so that they're scattered; hints starting with the same character and short hints are
    # spread evenly throughout the array.
    return hints.sort().map (str) -> str.reverse()

  getMatchingHints: (hintMarkers) ->
    matchString = @hintKeystrokeQueue.join ""
    linksMatched: hintMarkers.filter (linkMarker) -> linkMarker.hintString.startsWith matchString

  pushKeyChar: (keyChar, keydownKeyChar) ->
    @hintKeystrokeQueue.push (if @useKeydown then keydownKeyChar else keyChar)
  popKeyChar: -> @hintKeystrokeQueue.pop()

# Use numbers (usually) for hints, and also filter links by their text.
class FilterHints
  constructor: ->
    @linkHintNumbers = Settings.get "linkHintNumbers"
    @hintKeystrokeQueue = []
    @linkTextKeystrokeQueue = []
    @activeHintMarker = null
    # The regexp for splitting typed text and link texts.  We split on sequences of non-word characters and
    # link-hint numbers.
    @splitRegexp = new RegExp "[\\W#{Utils.escapeRegexSpecialCharacters @linkHintNumbers}]+"

  generateHintString: (linkHintNumber) ->
    base = @linkHintNumbers.length
    hint = []
    while 0 < linkHintNumber
      hint.push @linkHintNumbers[Math.floor linkHintNumber % base]
      linkHintNumber = Math.floor linkHintNumber / base
    hint.reverse().join ""

  renderMarker: (marker) ->
    marker.innerHTML = spanWrap(marker.hintString +
        (if marker.showLinkText then ": " + marker.linkText else ""))

  fillInMarkers: (hintMarkers) ->
    @renderMarker marker for marker in hintMarkers

    # We use @filterLinkHints() here (although we know that all of the hints will match) to fill in the hint
    # strings.  This ensures that we always get hint strings in the same order.
    @filterLinkHints hintMarkers

  getMatchingHints: (hintMarkers, tabCount = 0) ->
    # At this point, linkTextKeystrokeQueue and hintKeystrokeQueue have been updated to reflect the latest
    # input. use them to filter the link hints accordingly.
    matchString = @hintKeystrokeQueue.join ""
    linksMatched = @filterLinkHints hintMarkers
    linksMatched = linksMatched.filter (linkMarker) -> linkMarker.hintString.startsWith matchString

    # Visually highlight of the active hint (that is, the one that will be activated if the user
    # types <Enter>).
    tabCount = ((linksMatched.length * Math.abs tabCount) + tabCount) % linksMatched.length
    @activeHintMarker?.classList.remove "vimiumActiveHintMarker"
    @activeHintMarker = linksMatched[tabCount]
    @activeHintMarker?.classList.add "vimiumActiveHintMarker"

    linksMatched: linksMatched
    userMightOverType: @hintKeystrokeQueue.length == 0 and 0 < @linkTextKeystrokeQueue.length

  pushKeyChar: (keyChar, keydownKeyChar) ->
    # For filtered hints, we *always* use the keyChar value from keypress, because there is no obvious and
    # easy-to-understand meaning for choosing one of keyChar or keydownKeyChar (as there is for alphabet
    # hints).
    if 0 <= @linkHintNumbers.indexOf keyChar
      @hintKeystrokeQueue.push keyChar
    else
      # Since we might renumber the hints, we should reset the current hintKeyStrokeQueue.
      @hintKeystrokeQueue = []
      @linkTextKeystrokeQueue.push keyChar

  popKeyChar: ->
    @hintKeystrokeQueue.pop() or @linkTextKeystrokeQueue.pop()

  # Filter link hints by search string, renumbering the hints as necessary.
  filterLinkHints: (hintMarkers) ->
    linkSearchString = @linkTextKeystrokeQueue.join("").trim().toLowerCase()
    do (scoreFunction = @scoreLinkHint linkSearchString) ->
      linkMarker.score = scoreFunction linkMarker for linkMarker in hintMarkers
    hintMarkers = hintMarkers[..].sort (a,b) ->
      if b.score == a.score then b.stableSortCount - a.stableSortCount else b.score - a.score

    linkHintNumber = 1
    for linkMarker in hintMarkers
      continue unless 0 < linkMarker.score
      linkMarker.hintString = @generateHintString linkHintNumber++
      @renderMarker linkMarker
      linkMarker

  # Assign a score to a filter match (higher is better).  We assign a higher score for matches at the start of
  # a word, and a considerably higher score still for matches which are whole words.
  scoreLinkHint: (linkSearchString) ->
    searchWords = linkSearchString.trim().split @splitRegexp
    (linkMarker) =>
      text = linkMarker.linkText.trim()
      linkWords = linkMarker.linkWords ?= text.toLowerCase().split @splitRegexp

      searchWordScores =
        for searchWord in searchWords
          linkWordScores =
            for linkWord, idx in linkWords
              if linkWord == searchWord
                if idx == 0 then 8 else 6
              else if linkWord.startsWith searchWord
                if idx == 0 then 4 else 2
              else if 0 <= linkWord.indexOf searchWord
                1
              else
                0
          Math.max linkWordScores...

      if 0 in searchWordScores
        0
      else
        addFunc = (a,b) -> a + b
        score = searchWordScores.reduce addFunc, 0
        # Prefer matches in shorter texts.  To keep things balanced for links without any text, we just weight
        # them as if their length was 50.
        score / Math.log 1 + (text.length || 50)

#
# Make each hint character a span, so that we can highlight the typed characters as you type them.
#
spanWrap = (hintString) ->
  innerHTML = []
  for char in hintString
    innerHTML.push("<span class='vimiumReset'>" + char + "</span>")
  innerHTML.join("")

LocalHints =
  #
  # Determine whether the element is visible and clickable. If it is, find the rect bounding the element in
