diff options
| author | Stephen Blott | 2015-06-07 17:10:41 +0100 | 
|---|---|---|
| committer | Stephen Blott | 2015-06-08 07:25:25 +0100 | 
| commit | 3b22320266328d18f7ee606bcd6b2a2dfa5090b7 (patch) | |
| tree | 2660b7d94def82bf2b3802fbf2e9210d95a68c19 /content_scripts/link_hints.coffee | |
| parent | 44e4d7062d2f07895820178418001e5e1bf5ead7 (diff) | |
| download | vimium-3b22320266328d18f7ee606bcd6b2a2dfa5090b7.tar.bz2 | |
Refactor to keydown, as required.
Diffstat (limited to 'content_scripts/link_hints.coffee')
| -rw-r--r-- | content_scripts/link_hints.coffee | 225 | 
1 files changed, 107 insertions, 118 deletions
| diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 04853c22..87da34d6 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -20,18 +20,16 @@ DOWNLOAD_LINK_URL = name: "download"  LinkHints =    hintMarkerContainingDiv: null -  # one of the enums listed at the top of this file +  # One of the enums listed at the top of this file.    mode: undefined -  # function that does the appropriate action on the selected link +  # Function that does the appropriate action on the selected link.    linkActivator: undefined    # While in delayMode, all keypresses have no effect.    delayMode: false -  # Handle the link hinting marker generation and matching. Must be initialized after Settings have been -  # loaded, so that we can retrieve the option setting. -  getMarkerMatcher: -> -    if Settings.get("filterLinkHints") then filterHints else alphabetHints -  # lock to ensure only one instance runs at a time +  # 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 @@ -62,7 +60,8 @@ LinkHints =        length = (el) -> el.element.innerHTML?.length ? 0        elements.sort (a,b) -> length(a) - length b      hintMarkers = (@createMarkerFor(el) for el in elements) -    @getMarkerMatcher().fillInMarkers(hintMarkers) +    @markerMatcher = if Settings.get("filterLinkHints") then filterHints else alphabetHints +    @markerMatcher.fillInMarkers(hintMarkers)      @hintMode = new Mode        name: "hint/#{mode.name}" @@ -72,7 +71,8 @@ LinkHints =        exitOnEscape: true        exitOnClick: true        exitOnScroll: true -      keypress: @onKeyDownInMode.bind this, hintMarkers +      keydown: @onKeyDownInMode.bind this, hintMarkers +      keypress: @onKeyPressInMode.bind this, hintMarkers      @hintMode.onExit =>        @deactivateMode() if @isActive @@ -261,48 +261,67 @@ LinkHints =      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) -        @deactivateMode() -      else if (linksMatched.length == 1) -        @activateLink(linksMatched[0], delay) +    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 -        for marker in hintMarkers -          @hideMarker(marker) -        for matched in linksMatched -          @showMarker(matched, @getMarkerMatcher().hintKeystrokeQueue.length) -    false # We've handled this key, so prevent propagation. +        @deactivateMode() + +    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. @@ -340,17 +359,16 @@ LinkHints =    hideMarker: (linkMarker) -> linkMarker.style.display = "none" -  # If called without arguments, it executes immediately.  Othewise, it executes after 'delay' and invokes -  # 'callback' when it is finished. +  # If called without arguments, this exits immediately.  Othewise, it exits after 'delay'. After exiting, +  # 'callback' is invoked (if it is provided).    deactivateMode: (delay, callback) ->      deactivate = => -      if (LinkHints.getMarkerMatcher().deactivate) -        LinkHints.getMarkerMatcher().deactivate() -      if (LinkHints.hintMarkerContainingDiv) -        DomUtils.removeElement LinkHints.hintMarkerContainingDiv -      LinkHints.hintMarkerContainingDiv = null +      @markerMatcher?.deactivate?() +      DomUtils.removeElement @hintMarkerContainingDiv if @hintMarkerContainingDiv +      @hintMarkerContainingDiv = null        @isActive = false -      @hintMode.exit() +      @hintMode?.exit() +      @hintMode = null        @onExit?()        @onExit = null @@ -365,6 +383,7 @@ LinkHints =        callback?()  alphabetHints = +  activateOnEnter: false    hintKeystrokeQueue: []    logXOfBase: (x, base) -> Math.log(x) / Math.log(base) @@ -416,23 +435,17 @@ alphabetHints =        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) +  getMatchingHints: (hintMarkers) -> +    matchString = @hintKeystrokeQueue.join "" +    linksMatched: hintMarkers.filter (linkMarker) -> linkMarker.hintString.startsWith matchString -    matchString = @hintKeystrokeQueue.join("") -    linksMatched = hintMarkers.filter((linkMarker) -> linkMarker.hintString.indexOf(matchString) == 0) -    { linksMatched: linksMatched } +  pushKeyChar: (keyChar) -> @hintKeystrokeQueue.push keyChar +  popKeyChar: -> @hintKeystrokeQueue.pop()    deactivate: -> @hintKeystrokeQueue = []  filterHints = +  activateOnEnter: true    hintKeystrokeQueue: []    linkTextKeystrokeQueue: []    labelMap: {} @@ -495,67 +508,43 @@ filterHints =      hintMarkers -  matchHintsByKey: (hintMarkers, event) -> -    keyChar = String.fromCharCode event.charCode +  getMatchingHints: (hintMarkers) ->      delay = 0 -    userIsTypingLinkText = false -    if (event.keyCode == keyCodes.enter) -      # activate the lowest-numbered link hint that is visible -      for marker in hintMarkers -        if (marker.style.display != "none") -          return { linksMatched: [ marker ] } -    else if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) -      # backspace clears hint key queue first, then acts on link text key queue. -      # if both queues are empty. exit hinting mode -      if (!@hintKeystrokeQueue.pop() && !@linkTextKeystrokeQueue.pop()) -        return { linksMatched: [] } -    else if (keyChar) -      if (Settings.get("linkHintNumbers").indexOf(keyChar) >= 0) -        @hintKeystrokeQueue.push(keyChar) -      else -        # since we might renumber the hints, the current hintKeyStrokeQueue -        # should be rendered invalid (i.e. reset). -        @hintKeystrokeQueue = [] -        @linkTextKeystrokeQueue.push(keyChar) -        userIsTypingLinkText = true - -    # at this point, linkTextKeystrokeQueue and hintKeystrokeQueue have been updated to reflect the latest +    # At this point, linkTextKeystrokeQueue and hintKeystrokeQueue have been updated to reflect the latest      # input. use them to filter the link hints accordingly. -    linksMatched = @filterLinkHints(hintMarkers) -    matchString = @hintKeystrokeQueue.join("") -    linksMatched = linksMatched.filter((linkMarker) -> -      !linkMarker.filtered && linkMarker.hintString.indexOf(matchString) == 0) - -    if (linksMatched.length == 1 && userIsTypingLinkText) -      # In filter mode, people tend to type out words past the point -      # needed for a unique match. Hence we should avoid passing -      # control back to command mode immediately after a match is found. +    matchString = @hintKeystrokeQueue.join "" +    linksMatched = @filterLinkHints hintMarkers +    linksMatched = linksMatched.filter (linkMarker) -> linkMarker.hintString.startsWith matchString + +    if linksMatched.length == 1 && @hintKeystrokeQueue.length == 0 and 0 < @linkTextKeystrokeQueue.length +      # In filter mode, people tend to type out words past the point needed for a unique match. Hence we +      # should avoid passing control back to command mode immediately after a match is found.        delay = 200      { linksMatched: linksMatched, delay: delay } -  # -  # Marks the links that do not match the linkText search string with the 'filtered' DOM property. Renumbers -  # the remainder if necessary. -  # -  filterLinkHints: (hintMarkers) -> -    linksMatched = [] -    linkSearchString = @linkTextKeystrokeQueue.join("") +  pushKeyChar: (keyChar) -> +    if 0 <= Settings.get("linkHintNumbers").indexOf keyChar +      @hintKeystrokeQueue.push keyChar +    else +      # Since we might renumber the hints, we should reset the current hintKeyStrokeQueue. +      @hintKeystrokeQueue = [] +      @linkTextKeystrokeQueue.push keyChar -    for linkMarker in hintMarkers -      matchedLink = linkMarker.linkText.toLowerCase().indexOf(linkSearchString.toLowerCase()) >= 0 +  popKeyChar: -> +    @hintKeystrokeQueue.pop() or @linkTextKeystrokeQueue.pop() -      if (!matchedLink) -        linkMarker.filtered = true -      else -        linkMarker.filtered = false -        oldHintString = linkMarker.hintString -        linkMarker.hintString = @generateHintString(linksMatched.length) -        @renderMarker(linkMarker) if (linkMarker.hintString != oldHintString) -        linksMatched.push(linkMarker) +  # Filter link hints by search string, renumbering the hints as necessary. +  filterLinkHints: (hintMarkers) -> +    idx = 0 +    linkSearchString = @linkTextKeystrokeQueue.join("").toLowerCase() -    linksMatched +    for linkMarker in hintMarkers +      continue unless 0 <= linkMarker.linkText.toLowerCase().indexOf linkSearchString +      linkMarker.hintString = @generateHintString idx++ +      @renderMarker linkMarker +      linkMarker    deactivate: (delay, callback) ->      @hintKeystrokeQueue = [] | 
