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. |
