diff options
| -rw-r--r-- | README.md | 63 | ||||
| -rw-r--r-- | background_scripts/completion_engines.coffee | 10 | ||||
| -rw-r--r-- | background_scripts/completion_search.coffee | 6 | ||||
| -rw-r--r-- | background_scripts/main.coffee | 3 | ||||
| -rw-r--r-- | background_scripts/marks.coffee | 131 | ||||
| -rw-r--r-- | content_scripts/marks.coffee | 124 | ||||
| -rw-r--r-- | content_scripts/mode.coffee | 14 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 32 | 
8 files changed, 263 insertions, 120 deletions
| @@ -41,7 +41,7 @@ Navigating the current page:      F       open a link in a new tab      r       reload      gs      view source -    i       enter insert mode -- all commands will be ignored until you hit esc to exit +    i       enter insert mode -- all commands will be ignored until you hit Esc to exit      yy      copy the current url to the clipboard      yf      copy a link url to the clipboard      gf      cycle forward to the next frame @@ -56,11 +56,13 @@ Navigating to new pages:  Using find: -    /       enter find mode -- type your search query and hit enter to search or esc to cancel -            See here for advanced usage (regular expressions): https://github.com/philc/vimium/wiki/Find-Mode +    /       enter find mode +              -- type your search query and hit enter to search, or Esc to cancel      n       cycle forward to the next find match      N       cycle backward to the previous find match +For advanced usage, see [regular expressions](https://github.com/philc/vimium/wiki/Find-Mode) on the wiki. +  Navigating your history:      H       go back in history @@ -68,21 +70,28 @@ Navigating your history:  Manipulating tabs: -    J, gT      go one tab left -    K, gt      go one tab right -    g0         go to the first tab -    g$         go to the last tab -    t          create tab -    yt         duplicate current tab -    x          close current tab -    X          restore closed tab (i.e. unwind the 'x' command) -    T          search through your open tabs -    <a-p>      pin/unpin current tab +    J, gT   go one tab left +    K, gt   go one tab right +    g0      go to the first tab +    g$      go to the last tab +    t       create tab +    yt      duplicate current tab +    x       close current tab +    X       restore closed tab (i.e. unwind the 'x' command) +    T       search through your open tabs +    <a-p>   pin/unpin current tab + +Using marks: + +    ma, mA  set local mark "a" (global mark "A") +    `a, `A  jump to local mark "a" (global mark "A") +    ``      jump back to the position before the previous jump +              -- that is, before the previous gg, G, n, N, / or `a  Additional advanced browsing commands: -    ]]      Follow the link labeled 'next' or '>'. Helpful for browsing paginated sites. -    [[      Follow the link labeled 'previous' or '<'. Helpful for browsing paginated sites. +    ]], [[  Follow the link labeled 'next' or '>' ('previous' or '<') +              - helpful for browsing paginated sites      <a-f>   open multiple links in a new tab      gi      focus the first (or n-th) text input box on the page      gu      go up one level in the URL hierarchy @@ -92,7 +101,7 @@ Additional advanced browsing commands:      v       enter visual mode; use p/P to paste-and-go, use y to yank      V       enter visual line mode -Vimium supports command repetition so, for example, hitting `5t` will open 5 tabs in rapid succession. `<ESC>` (or +Vimium supports command repetition so, for example, hitting `5t` will open 5 tabs in rapid succession. `<Esc>` (or  `<c-[>`) will clear any partial commands in the queue and will also exit insert and find modes.  There are some advanced commands which aren't documented here; refer to the help dialog (type `?`) for a full @@ -101,8 +110,7 @@ list.  Custom Key Mappings  ------------------- -You may remap or unmap any of the default key bindings in the "Key mappings" section under "Advanced Options" -on the options page. +You may remap or unmap any of the default key bindings in the "Custom key mappings" on the options page.  Enter one of the following key mapping commands per line: @@ -119,10 +127,11 @@ Examples:  - `unmap <c-d>` removes any mapping for ctrl+d and restores Chrome's default behavior.  - `unmap r` removes any mapping for the r key. -Available Vimium commands can be found via the "Show Available Commands" link near the key mapping box. The -command name appears to the right of the description in parenthesis. +Available Vimium commands can be found via the "Show available commands" link +near the key mapping box on the options page. The command name appears to the +right of the description in parenthesis. -You can add comments to your key mappings by starting a line with `"` or `#`. +You can add comments to key mappings by starting a line with `"` or `#`.  The following special keys are available for mapping: @@ -150,7 +159,13 @@ Release Notes    ([here](https://github.com/philc/vimium/wiki/Search-Completion) and    [here](https://github.com/philc/vimium/wiki/Tips-and-Tricks#repeat-recent-queries)).  - A much improved interface for custom search engines. -- Bug fixes: bookmarklets accessed from the vomnibar. +- Added <tt>\`\`</tt> to jump back to the previous position after selected +  jump-like movements (`gg`, `G`, `/`, `n`, `N` and local mark movements). +- Global marks are now persistent across tab closes and browser sessions, and +  are synced between browser instances. +- Bug fixes, including: +    - Bookmarklets accessed from the vomnibar. +    - Global marks on non-Windows platforms.  1.51 (2015-05-02) @@ -373,7 +388,7 @@ does not support command repetition.  -  Bug fixes related to entering insert mode when the page first loads, and when focusing Flash embeds.  -  Added command listing to the Options page for easy reference.  -  `J` & `K` have reversed for tab switching: `J` goes left and `K` goes right. --  `<c-[>` is now equivalent to ESC, to match the behavior of VIM. +-  `<c-[>` is now equivalent to `Esc`, to match the behavior of VIM.  -  `<c-e>` and `<c-y>` are now mapped to scroll down and up respectively.  -  The characters used for link hints are now configurable under Advanced Options. @@ -383,7 +398,7 @@ does not support command repetition.  -  Command `yy` to yank (copy) the current tab's url to the clipboard.  -  Better Linux support.  -  Fix for `Shift+F` link hints. --  `ESC` now clears the keyQueue. So, for example, hitting `g`, `ESC`, `g` will no longer scroll the page. +-  `Esc` now clears the keyQueue. So, for example, hitting `g`, `Esc`, `g` will no longer scroll the page.  1.1 (2010-01-03) diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 9a88d491..dcbf99c6 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -46,14 +46,14 @@ class Google extends GoogleXMLRegexpEngine  class GoogleWithPrefix    constructor: (prefix, args...) ->      @engine = new Google args... -    @prefix = "#{prefix.trim()} " -    @queryTerms = @prefix.split /\s+/ +    @prefix = "#{prefix} " +    @queryTerms = prefix.split /\s+/    match: (args...) -> @engine.match args...    getUrl: (queryTerms) -> @engine.getUrl [ @queryTerms..., queryTerms... ]    parse: (xhr) -> -    @engine.parse(xhr) -      .filter (suggestion) => suggestion.startsWith @prefix -      .map (suggestion) => suggestion[@prefix.length..].ltrim() +    for suggestion in @engine.parse xhr +      continue unless suggestion.startsWith @prefix +      suggestion[@prefix.length..].ltrim()  # For Google Maps, we add the prefix "map of" to the query, and send it to Google's general search engine,  # then strip "map of" from the resulting suggestions. diff --git a/background_scripts/completion_search.coffee b/background_scripts/completion_search.coffee index d89eb278..b26194e6 100644 --- a/background_scripts/completion_search.coffee +++ b/background_scripts/completion_search.coffee @@ -12,7 +12,7 @@ CompletionSearch =    get: (searchUrl, url, callback) ->      xhr = new XMLHttpRequest()      xhr.open "GET", url, true -    xhr.timeout = 1000 +    xhr.timeout = 2500      xhr.ontimeout = xhr.onerror = -> callback null      xhr.send() @@ -115,8 +115,8 @@ CompletionSearch =                console.log "GET", url if @debug              catch                suggestions = [] -              # We allow failures to be cached too, but remove them after just thirty minutes. -              Utils.setTimeout 30 * 60 * 1000, => @completionCache.set completionCacheKey, null +              # We allow failures to be cached too, but remove them after just thirty seconds. +              Utils.setTimeout 30 * 1000, => @completionCache.set completionCacheKey, null                console.log "fail", url if @debug              callback suggestions diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 0d56e54d..835b8a9a 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -35,7 +35,8 @@ namedKeyRegex = /^(<(?:[amc]-.|(?:[amc]-)?[a-z0-9]{2,5})>)(.*)$/  # Event handlers  selectionChangedHandlers = [] -tabLoadedHandlers = {} # tabId -> function() +# Note. tabLoadedHandlers handlers is exported for use also by "marks.coffee". +root.tabLoadedHandlers = {} # tabId -> function()  # A secret, available only within the current instantiation of Vimium.  The secret is big, likely unguessable  # in practice, but less than 2^31. diff --git a/background_scripts/marks.coffee b/background_scripts/marks.coffee index 15d41205..6e5f08ba 100644 --- a/background_scripts/marks.coffee +++ b/background_scripts/marks.coffee @@ -1,34 +1,97 @@ -root = window.Marks = {} - -marks = {} - -root.create = (req, sender) -> -  marks[req.markName] = -    tabId: sender.tab.id -    scrollX: req.scrollX -    scrollY: req.scrollY - -chrome.tabs.onUpdated.addListener (tabId, changeInfo, tab) -> -  if changeInfo.url? -    removeMarksForTab tabId - -chrome.tabs.onRemoved.addListener (tabId, removeInfo) -> -  # XXX(jez): what about restored tabs? -  removeMarksForTab tabId - -removeMarksForTab = (id) -> -  for markName, mark of marks -    if mark.tabId is id -      delete marks[markName] - -root.goto = (req, sender) -> -  mark = marks[req.markName] -  chrome.tabs.update mark.tabId, selected: true -  chrome.tabs.sendMessage mark.tabId, -    name: "setScrollPosition" -    scrollX: mark.scrollX -    scrollY: mark.scrollY -  chrome.tabs.sendMessage mark.tabId, -    name: "showHUDforDuration", -    text: "Jumped to global mark '#{req.markName}'" -    duration: 1000 + +Marks = +  # This returns the key which is used for storing mark locations in chrome.storage.sync. +  getLocationKey: (markName) -> "vimiumGlobalMark|#{markName}" + +  # Get the part of a URL we use for matching here (that is, everything up to the first anchor). +  getBaseUrl: (url) -> url.split("#")[0] + +  # Create a global mark.  We record vimiumSecret with the mark so that we can tell later, when the mark is +  # used, whether this is the original Vimium session or a subsequent session.  This affects whether or not +  # tabId can be considered valid. +  create: (req, sender) -> +    chrome.storage.local.get "vimiumSecret", (items) => +      markInfo = +        vimiumSecret: items.vimiumSecret +        markName: req.markName +        url: @getBaseUrl sender.tab.url +        tabId: sender.tab.id +        scrollX: req.scrollX +        scrollY: req.scrollY + +      if markInfo.scrollX? and markInfo.scrollY? +        @saveMark markInfo +      else +        # The front-end frame hasn't provided the scroll position (because it's not the top frame within its +        # tab).  We need to ask the top frame what its scroll position is. (With the frame Id set to 0, below, +        # the request will only be handled by the top frame within the tab.) +        chrome.tabs.sendMessage sender.tab.id, name: "getScrollPosition", frameId: 0, (response) => +          @saveMark extend markInfo, scrollX: response.scrollX, scrollY: response.scrollY + +  saveMark: (markInfo) -> +    item = {} +    item[@getLocationKey markInfo.markName] = markInfo +    chrome.storage.sync.set item + +  # Goto a global mark.  We try to find the original tab.  If we can't find that, then we try to find another +  # tab with the original URL, and use that.  And if we can't find such an existing tab, then we create a new +  # one.  Whichever of those we do, we then set the scroll position to the original scroll position. +  goto: (req, sender) -> +    chrome.storage.local.get "vimiumSecret", (items) => +      vimiumSecret = items.vimiumSecret +      key = @getLocationKey req.markName +      chrome.storage.sync.get key, (items) => +        markInfo = items[key] +        if not markInfo +          # The mark is not defined. +          chrome.tabs.sendMessage sender.tab.id, +            name: "showHUDforDuration", +            text: "Global mark not set: '#{req.markName}'." +            duration: 1000 +        else if markInfo.vimiumSecret != vimiumSecret +          # This is a different Vimium instantiation, so markInfo.tabId is definitely out of date. +          @focusOrLaunch markInfo +        else +          # Check whether markInfo.tabId still exists.  According to here (https://developer.chrome.com/extensions/tabs), +          # tab Ids are unqiue within a Chrome session.  So, if we find a match, we can use it. +          chrome.tabs.get markInfo.tabId, (tab) => +            if not chrome.runtime.lastError and tab?.url and markInfo.url == @getBaseUrl tab.url +              # The original tab still exists. +              @gotoPositionInTab markInfo +            else +              # The original tab no longer exists. +              @focusOrLaunch markInfo + +  # Focus an existing tab and scroll to the given position within it. +  gotoPositionInTab: ({ tabId, scrollX, scrollY, markName }) -> +    chrome.tabs.update tabId, { selected: true }, -> +      chrome.tabs.sendMessage tabId, +        { name: "setScrollPosition", scrollX: scrollX, scrollY: scrollY }, -> +          chrome.tabs.sendMessage tabId, +            name: "showHUDforDuration", +            text: "Jumped to global mark '#{markName}'." +            duration: 1000 + +  # The tab we're trying to find no longer exists.  We either find another tab with a matching URL and use it, +  # or we create a new tab. +  focusOrLaunch: (markInfo) -> +    chrome.tabs.query { url: markInfo.url }, (tabs) => +      if 0 < tabs.length +        # We have a matching tab: use it (prefering, if there are more than one, one in the current window). +        @pickTabInWindow tabs, (tab) => +          @gotoPositionInTab extend markInfo, tabId: tab.id +      else +        # There is no existing matching tab, we'll have to create one. +        chrome.tabs.create { url: @getBaseUrl markInfo.url }, (tab) => +          # Note. tabLoadedHandlers is defined in "main.coffee".  The handler below will be called when the tab +          # is loaded, its DOM is ready and it registers with the background page. +          tabLoadedHandlers[tab.id] = => @gotoPositionInTab extend markInfo, tabId: tab.id + +  # Given a list of tabs, pick one in the current window, if possible, otherwise just pick any. +  pickTabInWindow: (tabs, continuation) -> +    chrome.windows.getCurrent ({ id }) -> +      tabsInWindow = tabs.filter (tab) -> tab.windowId == id +      continuation tabsInWindow[0] ? tabs[0] + +root = exports ? window +root.Marks = Marks diff --git a/content_scripts/marks.coffee b/content_scripts/marks.coffee index 316ab951..067d05a8 100644 --- a/content_scripts/marks.coffee +++ b/content_scripts/marks.coffee @@ -1,45 +1,79 @@ -root = window.Marks = {} - -root.activateCreateMode = -> -  handlerStack.push keydown: (e) -> -    keyChar = KeyboardUtils.getKeyChar(event) -    return unless keyChar isnt "" - -    if /[A-Z]/.test keyChar -      chrome.runtime.sendMessage { -        handler: 'createMark', -        markName: keyChar -        scrollX: window.scrollX, -        scrollY: window.scrollY -      }, -> HUD.showForDuration "Created global mark '#{keyChar}'", 1000 -    else if /[a-z]/.test keyChar -      [baseLocation, sep, hash] = window.location.href.split '#' -      localStorage["vimiumMark|#{baseLocation}|#{keyChar}"] = JSON.stringify -        scrollX: window.scrollX, -        scrollY: window.scrollY -      HUD.showForDuration "Created local mark '#{keyChar}'", 1000 - -    @remove() - -    false - -root.activateGotoMode = -> -  handlerStack.push keydown: (e) -> -    keyChar = KeyboardUtils.getKeyChar(event) -    return unless keyChar isnt "" - -    if /[A-Z]/.test keyChar -      chrome.runtime.sendMessage -        handler: 'gotoMark' -        markName: keyChar -    else if /[a-z]/.test keyChar -      [baseLocation, sep, hash] = window.location.href.split '#' -      markString = localStorage["vimiumMark|#{baseLocation}|#{keyChar}"] -      if markString? -        mark = JSON.parse markString -        window.scrollTo mark.scrollX, mark.scrollY -        HUD.showForDuration "Jumped to local mark '#{keyChar}'", 1000 - -    @remove() - -    false + +Marks = +  previousPositionRegisters: [ "`", "'" ] +  localRegisters: {} +  mode: null + +  exit: (continuation = null) -> +    @mode?.exit() +    @mode = null +    continuation?() + +  # This returns the key which is used for storing mark locations in localStorage. +  getLocationKey: (keyChar) -> +    "vimiumMark|#{window.location.href.split('#')[0]}|#{keyChar}" + +  getMarkString: -> +    JSON.stringify scrollX: window.scrollX, scrollY: window.scrollY + +  setPreviousPosition: -> +    markString = @getMarkString() +    @localRegisters[reg] = markString for reg in @previousPositionRegisters + +  showMessage: (message, keyChar) -> +    HUD.showForDuration "#{message} \"#{keyChar}\".", 1000 + +  # If <Shift> is depressed, then it's a global mark, otherwise it's a local mark.  This is consistent +  # vim's [A-Z] for global marks and [a-z] for local marks.  However, it also admits other non-Latin +  # characters.  The exceptions are "`" and "'", which are always considered local marks. +  isGlobalMark: (event, keyChar) -> +    event.shiftKey and keyChar not in @previousPositionRegisters + +  activateCreateMode: -> +    @mode = new Mode +      name: "create-mark" +      indicator: "Create mark..." +      exitOnEscape: true +      suppressAllKeyboardEvents: true +      keypress: (event) => +        keyChar = String.fromCharCode event.charCode +        @exit => +          if @isGlobalMark event, keyChar +            # We record the current scroll position, but only if this is the top frame within the tab. +            # Otherwise, we'll fetch the scroll position of the top frame from the background page later. +            [ scrollX, scrollY ] = [ window.scrollX, window.scrollY ] if DomUtils.isTopFrame() +            chrome.runtime.sendMessage +              handler: 'createMark' +              markName: keyChar +              scrollX: scrollX +              scrollY: scrollY +            , => @showMessage "Created global mark", keyChar +          else +            localStorage[@getLocationKey keyChar] = @getMarkString() +            @showMessage "Created local mark", keyChar + +  activateGotoMode: (registryEntry) -> +    @mode = new Mode +      name: "goto-mark" +      indicator: "Go to mark..." +      exitOnEscape: true +      suppressAllKeyboardEvents: true +      keypress: (event) => +        @exit => +          keyChar = String.fromCharCode event.charCode +          if @isGlobalMark event, keyChar +            chrome.runtime.sendMessage +              handler: 'gotoMark' +              markName: keyChar +          else +            markString = @localRegisters[keyChar] ? localStorage[@getLocationKey keyChar] +            if markString? +              @setPreviousPosition() +              position = JSON.parse markString +              window.scrollTo position.scrollX, position.scrollY +              @showMessage "Jumped to local mark", keyChar +            else +              @showMessage "Local mark not set", keyChar + +root = exports ? window +root.Marks =  Marks diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee index f631b4cd..ffabc111 100644 --- a/content_scripts/mode.coffee +++ b/content_scripts/mode.coffee @@ -47,6 +47,13 @@ class Mode      @id = "#{@name}-#{@count}"      @log "activate:", @id +    # If options.suppressAllKeyboardEvents is truthy, then all keyboard events are suppressed.  This avoids +    # the need for modes which suppress all keyboard events 1) to provide handlers for all of those events, +    # or 2) to worry about event suppression and event-handler return values. +    if @options.suppressAllKeyboardEvents +      for type in [ "keydown", "keypress", "keyup" ] +        @options[type] = @alwaysSuppressEvent @options[type] +      @push        keydown: @options.keydown || null        keypress: @options.keypress || null @@ -171,6 +178,13 @@ class Mode    # case), because they do not need to be concerned with the value they yield.    alwaysContinueBubbling: handlerStack.alwaysContinueBubbling +  # Shorthand for an event handler which always suppresses event propagation. +  alwaysSuppressEvent: (handler = null) -> +    (event) => +      handler? event +      DomUtils.suppressPropagation event +      @stopBubblingAndFalse +    # Activate a new instance of this mode, together with all of its original options (except its main    # keybaord-event handlers; these will be recreated).    cloneMode: -> diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 7ad75514..3055ecea 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -142,12 +142,12 @@ initializePreDomReady = ->      window.removeEventListener "focus", onFocus    requestHandlers = -    showHUDforDuration: (request) -> HUD.showForDuration request.text, request.duration +    showHUDforDuration: handleShowHUDforDuration      toggleHelpDialog: (request) -> toggleHelpDialog(request.dialogHtml, request.frameId)      focusFrame: (request) -> if (frameId == request.frameId) then focusThisFrame request      refreshCompletionKeys: refreshCompletionKeys      getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY -    setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY +    setScrollPosition: setScrollPosition      executePageCommand: executePageCommand      currentKeyQueue: (request) ->        keyQueue = request.keyQueue @@ -241,9 +241,10 @@ unregisterFrame = ->      tab_is_closing: DomUtils.isTopFrame()  executePageCommand = (request) -> +  commandType = request.command.split(".")[0]    # Vomnibar commands are handled in the tab's main/top frame.  They are handled even if Vimium is otherwise    # disabled in the frame. -  if request.command.split(".")[0] == "Vomnibar" +  if commandType == "Vomnibar"      if DomUtils.isTopFrame()        # We pass the frameId from request.  That's the frame which originated the request, so that's the frame        # which should receive the focus when the vomnibar closes. @@ -261,9 +262,18 @@ executePageCommand = (request) ->    refreshCompletionKeys(request) -setScrollPosition = (scrollX, scrollY) -> -  if (scrollX > 0 || scrollY > 0) -    DomUtils.documentReady(-> window.scrollTo(scrollX, scrollY)) +handleShowHUDforDuration = ({ text, duration }) -> +  if DomUtils.isTopFrame() +    DomUtils.documentReady -> HUD.showForDuration text, duration + +setScrollPosition = ({ scrollX, scrollY }) -> +  if DomUtils.isTopFrame() +    DomUtils.documentReady -> +      window.focus() +      document.body.focus() +      if 0 < scrollX or 0 < scrollY +        Marks.setPreviousPosition() +        window.scrollTo scrollX, scrollY  #  # Called from the backend in order to change frame focus. @@ -299,8 +309,12 @@ window.focusThisFrame = do ->        setTimeout (-> highlightedFrameElement.remove()), 200  extend window, -  scrollToBottom: -> Scroller.scrollTo "y", "max" -  scrollToTop: -> Scroller.scrollTo "y", 0 +  scrollToBottom: -> +    Marks.setPreviousPosition() +    Scroller.scrollTo "y", "max" +  scrollToTop: -> +    Marks.setPreviousPosition() +    Scroller.scrollTo "y", 0    scrollToLeft: -> Scroller.scrollTo "x", 0    scrollToRight: -> Scroller.scrollTo "x", "max"    scrollUp: -> Scroller.scrollBy "y", -1 * Settings.get("scrollStepSize") @@ -861,6 +875,7 @@ window.getFindModeQuery = (backwards) ->      findModeQuery.parsedQuery  findAndFocus = (backwards) -> +  Marks.setPreviousPosition()    query = getFindModeQuery backwards    findModeQueryHasResults = @@ -1007,6 +1022,7 @@ findModeRestoreSelection = (range = findModeInitialRange) ->  # Enters find mode.  Returns the new find-mode instance.  window.enterFindMode = (options = {}) -> +  Marks.setPreviousPosition()    # Save the selection, so performFindInPlace can restore it.    findModeSaveSelection()    findModeQuery = rawQuery: "" | 
