diff options
| -rw-r--r-- | content_scripts/mode.coffee | 15 | ||||
| -rw-r--r-- | content_scripts/mode_find.coffee | 2 | ||||
| -rw-r--r-- | content_scripts/mode_visual_edit.coffee | 83 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 4 | ||||
| -rw-r--r-- | lib/utils.coffee | 23 | 
5 files changed, 67 insertions, 60 deletions
| diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee index e7a4e0ee..bda8672c 100644 --- a/content_scripts/mode.coffee +++ b/content_scripts/mode.coffee @@ -91,10 +91,11 @@ class Mode      # be unique.  New instances deactivate existing instances with the same key.      if @options.singleton        do => +        singletons = Mode.singletons ||= {} +        key = Utils.getIdentity @options.singleton +        @onExit -> delete singletons[key]          @deactivateSingleton @options.singleton -        @onExit => Mode.singletons = Mode.singletons.filter (active) => active.key != @options.singleton -        Mode.singletons.push key: @options.singleton, mode: @ -        console.log "singletons:", (Mode.singletons.map (active) -> active.mode.id)... if @debug +        singletons[key] = @      # If @options.trackState is truthy, then the mode mainatins the current state in @enabled and @passKeys,      # and calls @registerStateChange() (if defined) whenever the state changes. The mode also tracks the @@ -137,12 +138,8 @@ class Mode        Mode.updateBadge()        @modeIsActive = false -  deactivateSingleton: (key) -> -    Mode.singletons ||= [] -    for active in Mode.singletons -      if active.key == key and active.mode.modeIsActive -        console.log "singleton, deactivating:", active.mode.id if @debug -        active.mode.exit() +  deactivateSingleton: (singleton) -> +    Mode.singletons?[Utils.getIdentity singleton]?.exit()    # The badge is chosen by bubbling an "updateBadge" event down the handler stack allowing each mode the    # opportunity to choose a badge. This is overridden in sub-classes. diff --git a/content_scripts/mode_find.coffee b/content_scripts/mode_find.coffee index c0b97b16..67f2a7dc 100644 --- a/content_scripts/mode_find.coffee +++ b/content_scripts/mode_find.coffee @@ -35,7 +35,7 @@ class PostFindMode extends SuppressPrintable        name: "post-find"        # We show a "?" badge, but only while an Escape activates insert mode.        badge: "?" -      # Important. PostFindMode shares a singleton with the modes launched by focusInput. +      # PostFindMode shares a singleton with the modes launched by focusInput; each displaces the other.        singleton: element        exitOnBlur: element        exitOnClick: true diff --git a/content_scripts/mode_visual_edit.coffee b/content_scripts/mode_visual_edit.coffee index d7188c1f..5772e28a 100644 --- a/content_scripts/mode_visual_edit.coffee +++ b/content_scripts/mode_visual_edit.coffee @@ -29,8 +29,7 @@ class CountPrefix extends SuppressPrintable      super options      @countPrefix = "" -    @countPrefixFactor = 1 -    @countPrefixFactor = @getCountPrefix options.initialCountPrefix if options.initialCountPrefix +    @countPrefixFactor = options.initialCountPrefix || 1      @push        _name: "#{@id}/maintain-count" @@ -46,7 +45,6 @@ class CountPrefix extends SuppressPrintable    # This handles both "d3w" and "3dw". Also, "3d2w" deletes six words.    getCountPrefix: (prefix = @countPrefix) -> -    prefix = prefix.toString() if typeof prefix == "number"      count = @countPrefixFactor * if 0 < prefix?.length then parseInt prefix else 1      @countPrefix = ""      @countPrefixFactor = 1 @@ -131,12 +129,12 @@ class Movement extends CountPrefix      forward    # An approximation of the vim "w" movement; only ever used in the forward direction. -  moveForwardWord: -> +  moveForwardWord: (count = 1) ->      # First, move to the start of the current word...      @runMovement forward, character      @runMovement backward, "word"      # And then to the start of the next word... -    @selectLexicalEntity "word" +    @selectLexicalEntity "word", count      return      # Previous version...      # This works in normal text inputs, but not in some contentEditable elements (notably the compose window @@ -174,9 +172,9 @@ class Movement extends CountPrefix      "0": "backward lineboundary"      "G": "forward documentboundary"      "g": "backward documentboundary" -    "Y": -> @selectLexicalEntity "lineboundary" -    "w": -> @moveForwardWord() -    "o": -> @reverseSelection() +    "Y": (count) -> @selectLexicalEntity "lineboundary", count +    "w": (count) -> @moveForwardWord count +    "o": (count) -> @reverseSelection()    constructor: (options) ->      @selection = window.getSelection() @@ -192,8 +190,8 @@ class Movement extends CountPrefix      @movements.W = @movements.w      if @options.immediateMovement -      # This instance has been created just to run a single movement only and then yank the result. -      @handleMovementKeyChar @options.immediateMovement +      # This instance has been created just to run a single movement then yank the result. +      @handleMovementKeyChar @options.immediateMovement, @getCountPrefix()        @yank()        return @@ -221,14 +219,17 @@ class Movement extends CountPrefix                  return @suppressEvent          @continueBubbling +    # +    # End of Movement constructor.    handleMovementKeyChar: (keyChar, count = 1) -> -    action = -      switch typeof @movements[keyChar] -        when "string" then => @runMovement @movements[keyChar] -        when "function" then => @movements[keyChar].call @ +    console.log "xxx", keyChar, count      @protectClipboard => -      action() for [0...count] +      switch typeof @movements[keyChar] +        when "string" +          @runMovement @movements[keyChar] for [0...count] +        when "function" +          @movements[keyChar].call @, count        @scrollIntoView()    # Yank the selection; always exits; either deletes the selection or collapses it; returns the yanked text. @@ -306,7 +307,7 @@ class VisualMode extends Movement            return        when "Caret"          # Try to start with a visible selection. -        @extendByOneCharacter(forward) or @extendByOneCharacter backward unless options.editModeParent +        @extendByOneCharacter(forward) or @extendByOneCharacter backward unless options.parentMode          @scrollIntoView() if @selection.type == "Range"      defaults = @@ -319,17 +320,21 @@ class VisualMode extends Movement      # Additional commands when not being run only for movement.      unless @options.oneMovementOnly        @commands.y = -> @yank() -      @commands.V = -> new VisualLineMode        @commands.p = -> chrome.runtime.sendMessage handler: "openUrlInCurrentTab", url: @yank()        @commands.P = -> chrome.runtime.sendMessage handler: "openUrlInNewTab", url: @yank() - -    # Additional commands when run under edit mode (but not just for movement). -    if @options.editModeParent and not @options.oneMovementOnly +      @commands.V = -> +        if @options.parentMode +          @options.parentMode.launchSubMode VisualLineMode +        else +          new VisualLineMode + +    # Additional commands when run under edit mode (except if only for one movement). +    if @options.parentMode and not @options.oneMovementOnly          @commands.x = -> @yank deleteFromDocument: true          @commands.d = -> @yank deleteFromDocument: true          @commands.c = ->            @yank deleteFromDocument: true -          @options.editModeParent.enterInsertMode() +          @options.parentMode.enterInsertMode()      # For "yy" and "dd".      if @options.yankLineCharacter @@ -351,7 +356,7 @@ class VisualMode extends Movement                    @selectLexicalEntity entity, count                    @yank() -    unless @options.editModeParent +    unless @options.parentMode        @installFindMode()      # Grab the initial clipboard contents.  We try to keep them intact until we get an explicit yank. @@ -371,7 +376,7 @@ class VisualMode extends Movement      super @clipboardContents = text    exit: (event, target) -> -    unless @options.editModeParent +    unless @options.parentMode        # Don't leave the user in insert mode just because they happen to have selected text within an input        # element.        if document.activeElement and DomUtils.isEditable document.activeElement @@ -412,8 +417,8 @@ class VisualMode extends Movement              range.setStart newFindRange.startContainer, newFindRange.startOffset              @selectRange range -    @movements.n = -> executeFind false -    @movements.N = -> executeFind true +    @movements.n = (count) -> executeFind false +    @movements.N = (count) -> executeFind true    # When visual mode starts and there's no existing selection, we try to establish one.  As a heuristic, we    # pick the first non-whitespace character of the first visible text node which seems to be long enough to be @@ -439,6 +444,12 @@ class VisualLineMode extends VisualMode      super extend { name: "visual/line" }, options      @extendSelection() +    @commands.v = -> +      if @options.parentMode +        @options.parentMode.launchSubMode VisualMode +      else +        new VisualMode +    handleMovementKeyChar: (keyChar) ->      super keyChar      @extendSelection() @@ -471,9 +482,10 @@ class EditMode extends Movement        p: -> @pasteClipboard forward        P: -> @pasteClipboard backward        v: -> @launchSubMode VisualMode +      V: -> @launchSubMode VisualLineMode -      Y: (count) -> @enterVisualModeForMovement 1, immediateMovement: "Y" -      x: (count) -> @enterVisualModeForMovement count, immediateMovement: "h", deleteFromDocument: true +      Y: (count) -> @enterVisualModeForMovement count, immediateMovement: "Y" +      x: (count) -> @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true        X: (count) -> @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true        y: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "y"        d: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "d", deleteFromDocument: true @@ -501,9 +513,11 @@ class EditMode extends Movement        targetElement: @options.targetElement    launchSubMode: (mode, options = {}) -> -    @lastSubMode = +    @activeSubMode?.instance.exit() +    @activeSubMode =        mode: mode -      instance: new mode extend options, editModeParent: @ +      instance: new mode extend options, parentMode: @ +    @activeSubMode.instance.onExit => @activeSubMode = null    pasteClipboard: (direction) ->      @paste (text) => @@ -535,10 +549,9 @@ class EditMode extends Movement    exit: (event, target) ->      super event, target -    @lastSubMode = -      if @lastSubMode?.instance.modeIsActive -        @lastSubMode.instance.exit event, target -        @lastSubMode +    # Deactivate any active sub-mode. Any such mode will clear @activeSubMode on exit, so we grab a copy now. +    activeSubMode = @activeSubMode +    activeSubMode?.instance.exit()      if event?.type == "keydown" and KeyboardUtils.isEscape event        if target? and DomUtils.isDOMDescendant @element, target @@ -565,8 +578,8 @@ class EditMode extends Movement              if event?.target == @options.targetElement                console.log "#{@id}: reactivating edit mode" if @debug                editMode = new EditMode @getConfigurationOptions() -              if @lastSubMode -                editMode.launchSubMode @lastSubMode.mode, @lastSubMode.instance.getConfigurationOptions() +              if activeSubMode +                editMode.launchSubMode activeSubMode.mode, activeSubMode.instance.getConfigurationOptions()  root = exports ? window  root.VisualMode = VisualMode diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index d1da9524..b8e4149b 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -393,7 +393,7 @@ extend window,                  selectedInputIndex += hints.length + (if event.shiftKey then -1 else 1)                  selectedInputIndex %= hints.length                  hints[selectedInputIndex].classList.add 'internalVimiumSelectedInputHint' -                # Deactivate any other modes on this element. +                # Deactivate any active modes on this element (PostFindMode, or a suspended edit mode).                  @deactivateSingleton visibleInputs[selectedInputIndex].element                  visibleInputs[selectedInputIndex].element.focus()                  @suppressEvent @@ -406,7 +406,7 @@ extend window,              id: "vimiumInputMarkerContainer"              className: "vimiumReset" -          # Deactivate any other modes on this element. +          # Deactivate any active modes on this element (PostFindMode, or a suspended edit mode).            @deactivateSingleton visibleInputs[selectedInputIndex].element            visibleInputs[selectedInputIndex].element.focus()            if visibleInputs.length == 1 diff --git a/lib/utils.coffee b/lib/utils.coffee index f8eb5457..c04bf417 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -152,19 +152,16 @@ Utils =    # locale-sensitive uppercase detection    hasUpperCase: (s) -> s.toLowerCase() != s -  # Allow a function call to be suppressed.  Use Utils.suppress.unlessSuppressed to call a function, unless it -  # is suppressed via the given key.  Use Utils.suppressor.suppress to call a function while suppressing the -  # given key. -  suppressor: do -> -    suppressed = {} - -    suppress: (key, func) -> -      suppressed[key] = if suppressed[key]? then suppressed[key] + 1 else 1 -      func() -      suppressed[key] -= 1 - -    unlessSuppressed: (key, func) -> -      func() unless suppressed[key]? and 0 < suppressed[key] +  # Give objects (including elements) distinct identities. +  getIdentity: do -> +    identities = [] + +    (obj) -> +      index = identities.indexOf obj +      if index < 0 +        index = identities.length +        identities.push obj +      "identity-" + index  # This creates a new function out of an existing function, where the new function takes fewer arguments. This  # allows us to pass around functions instead of functions + a partial list of arguments. | 
