aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--content_scripts/mode.coffee15
-rw-r--r--content_scripts/mode_find.coffee2
-rw-r--r--content_scripts/mode_visual_edit.coffee83
-rw-r--r--content_scripts/vimium_frontend.coffee4
-rw-r--r--lib/utils.coffee23
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.