aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--background_scripts/commands.coffee3
-rw-r--r--content_scripts/link_hints.coffee21
-rw-r--r--content_scripts/mode_visual_edit.coffee361
-rw-r--r--content_scripts/vimium_frontend.coffee35
-rw-r--r--lib/dom_utils.coffee20
-rw-r--r--lib/keyboard_utils.coffee2
-rw-r--r--manifest.json2
8 files changed, 97 insertions, 356 deletions
diff --git a/README.md b/README.md
index fe3475be..c914e864 100644
--- a/README.md
+++ b/README.md
@@ -145,7 +145,9 @@ Shifts are automatically detected so, for example, `<c-&>` corresponds to ctrl+s
More documentation
------------------
-Many of the more advanced or involved features are documented on [Vimium's github wiki](https://github.com/philc/vimium/wiki).
+Many of the more advanced or involved features are documented on
+[Vimium's github wiki](https://github.com/philc/vimium/wiki). Also
+see the [FAQ](https://github.com/philc/vimium/wiki/FAQ).
Contributing
------------
@@ -155,8 +157,9 @@ Release Notes
-------------
Next version (not yet released)
-- Bug fixes:
- - Fix endless scrolling (#1911).
+1.54 (2016-01-30)
+
+- Fix occasional endless scrolling (#1911).
1.53 (2015-09-25)
diff --git a/background_scripts/commands.coffee b/background_scripts/commands.coffee
index 50306c35..0a296c3e 100644
--- a/background_scripts/commands.coffee
+++ b/background_scripts/commands.coffee
@@ -98,7 +98,6 @@ Commands =
"enterInsertMode",
"enterVisualMode",
"enterVisualLineMode",
- # "enterEditMode",
"focusInput",
"LinkHints.activateMode",
"LinkHints.activateModeToOpenInNewTab",
@@ -187,7 +186,6 @@ defaultKeyMappings =
"i": "enterInsertMode"
"v": "enterVisualMode"
"V": "enterVisualLineMode"
- # "gv": "enterEditMode"
"H": "goBack"
"L": "goForward"
@@ -279,7 +277,6 @@ commandDescriptions =
enterInsertMode: ["Enter insert mode", { noRepeat: true }]
enterVisualMode: ["Enter visual mode", { noRepeat: true }]
enterVisualLineMode: ["Enter visual line mode", { noRepeat: true }]
- # enterEditMode: ["Enter vim-like edit mode (not yet implemented)", { noRepeat: true }]
focusInput: ["Focus the first text box on the page. Cycle between them using tab",
{ passCountToFunction: true }]
diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee
index 07deaf61..54476935 100644
--- a/content_scripts/link_hints.coffee
+++ b/content_scripts/link_hints.coffee
@@ -58,6 +58,11 @@ class LinkHintsMode
# ancestors.
length = (el) -> el.element.innerHTML?.length ? 0
elements.sort (a,b) -> length(a) - length b
+
+ if elements.length == 0
+ HUD.showForDuration "No links to select.", 2000
+ return
+
hintMarkers = (@createMarkerFor(el) for el in elements)
@markerMatcher = new (if Settings.get "filterLinkHints" then FilterHints else AlphabetHints)
@markerMatcher.fillInMarkers hintMarkers
@@ -70,7 +75,6 @@ class LinkHintsMode
suppressTrailingKeyEvents: true
exitOnEscape: true
exitOnClick: true
- exitOnScroll: true
keydown: @onKeyDownInMode.bind this, hintMarkers
keypress: @onKeyPressInMode.bind this, hintMarkers
@@ -120,7 +124,7 @@ class LinkHintsMode
DomUtils.simulateClick link, altKey: true, ctrlKey: false, metaKey: false
else # OPEN_IN_CURRENT_TAB
@hintMode.setIndicator "Open link in current tab."
- @linkActivator = (link) -> DomUtils.simulateClick.bind(DomUtils, link)()
+ @linkActivator = DomUtils.simulateClick.bind DomUtils
#
# Creates a link marker for the given link.
@@ -590,7 +594,8 @@ class FilterHints
scoreLinkHint: (linkSearchString) ->
searchWords = linkSearchString.trim().split /\s+/
(linkMarker) ->
- linkWords = linkMarker.linkWords ?= linkMarker.linkText.trim().toLowerCase().split /\s+/
+ text = linkMarker.linkText.trim()
+ linkWords = linkMarker.linkWords ?= text.toLowerCase().split /\s+/
searchWordScores =
for searchWord in searchWords
@@ -606,8 +611,14 @@ class FilterHints
0
Math.max linkWordScores...
- addFunc = (a,b) -> a + b
- if 0 in searchWordScores then 0 else searchWordScores.reduce addFunc, 0
+ if 0 in searchWordScores
+ 0
+ else
+ addFunc = (a,b) -> a + b
+ score = searchWordScores.reduce addFunc, 0
+ # Prefer matches in shorter texts. To keep things balanced for links without any text, we just weight
+ # them as if their length was 50.
+ score / Math.log(text.length || 50)
#
# Make each hint character a span, so that we can highlight the typed characters as you type them.
diff --git a/content_scripts/mode_visual_edit.coffee b/content_scripts/mode_visual_edit.coffee
index 8bcde6cb..ae8897ca 100644
--- a/content_scripts/mode_visual_edit.coffee
+++ b/content_scripts/mode_visual_edit.coffee
@@ -4,13 +4,12 @@
# - VisualMode
# - VisualLineMode
# - CaretMode
-# - EditMode (experimental)
#
# SuppressPrintable and CountPrefix are shared utility base classes.
# Movement is a shared vim-like movement base class.
#
# The class inheritance hierarchy is:
-# - Mode, SuppressPrintable, CountPrefix, Movement, [ VisualMode | CaretMode | EditMode ]
+# - Mode, SuppressPrintable, CountPrefix, Movement, [ VisualMode | CaretMode ]
# - Mode, SuppressPrintable, CountPrefix, Movement, VisualMode, VisualLineMode
#
# The possible mode states are:
@@ -20,10 +19,6 @@
# - ..., VisualMode, FindMode
# - ..., VisualLineMode, FindMode
# - ..., CaretMode, FindMode
-# - ..., EditMode
-# - ..., EditMode, InsertMode
-# - ..., EditMode, VisualMode
-# - ..., EditMode, VisualLineMode
#
# This prevents printable characters from being passed through to underlying modes or the underlying page.
@@ -44,8 +39,7 @@ class SuppressPrintable extends Mode
class CountPrefix extends SuppressPrintable
constructor: (options) ->
@countPrefix = ""
- # This is an initial multiplier for the first count. It allows edit mode to implement both "d3w" and
- # "3dw". Also, "3d2w" deletes six words.
+ # This is an initial multiplier for the first count.
@countPrefixFactor = options.initialCountPrefix || 1
super options
@@ -93,10 +87,9 @@ class Movement extends CountPrefix
# disable copy so that subsequent copies do not propagate.
@copy = (->) if isFinalUserCopy
- # This s used whenever manipulating the selection may, as a side effect, change the clipboard's contents.
+ # This is used whenever manipulating the selection may, as a side effect, change the clipboard's contents.
# We restore the original clipboard contents when we're done. May be asynchronous. We use a lock so that
- # calls can be nested. We do this primarily for edit mode, where the user does not expect caret movements
- # to change the clipboard contents.
+ # calls can be nested.
protectClipboard: do ->
locked = false
@@ -111,10 +104,7 @@ class Movement extends CountPrefix
# mode with visual-line mode.
changeMode: (mode, options = {}) ->
@exit()
- if @options.parentMode
- @options.parentMode.launchSubMode mode, options
- else
- new mode options
+ new mode options
# Return the character following (to the right of) the focus, and leave the selection unchanged. Returns
# undefined if no such character exists.
@@ -287,20 +277,6 @@ class Movement extends CountPrefix
when "function" then @movements[keyChar].call @, count
@scrollIntoView()
- # The bahavior of Movement can be tweaked by setting the following options:
- # - options.parentMode (a mode)
- # This instance is a sub-mode of another mode (currently, only edit mode).
- # - options.oneMovementOnly (truthy/falsy)
- # This instance is created for one movement only, after which it yanks and exits.
- # - options.immediateMovement (a keyChar string)
- # This instance is created for one movement only, and this options specifies the movement (e.g. "j").
- # - options.deleteFromDocument (truthy/falsy)
- # When yanking text, also delete it from the document.
- # - options.onYank (a function)
- # When yanking text, also call this function, passing the yanked text as an argument.
- # - options.noCopyToClipboard (truthy/falsy)
- # If truthy, then do not copy the yanked text to the clipboard when yanking.
- #
constructor: (options) ->
@selection = window.getSelection()
@movements = extend {}, @movements
@@ -312,13 +288,8 @@ class Movement extends CountPrefix
@movements.B = @movements.b
@movements.W = @movements.w
- if @options.immediateMovement
- # This instance has been created to execute a single, given movement.
- @runMovementKeyChar @options.immediateMovement, @getCountPrefix()
- return
-
# This is the main keyboard-event handler for movements and commands for all user modes (visual,
- # visual-line, caret and edit).
+ # visual-line and caret).
@push
_name: "#{@id}/keypress"
keypress: (event) =>
@@ -346,27 +317,24 @@ class Movement extends CountPrefix
@continueBubbling
- # Install basic bindings for find mode, "n" and "N". We do not install these bindings if this is a
- # sub-mode of edit mode (because we cannot guarantee that the selection will remain within the active
- # element), or if this instance has been created to execute only a single movement.
- unless @options.parentMode or options.oneMovementOnly
- do =>
- doFind = (count, backwards) =>
- initialRange = @selection.getRangeAt(0).cloneRange()
- for [0...count] by 1
- unless FindMode.execute null, {colorSelection: false, backwards}
- @setSelectionRange initialRange
- HUD.showForDuration("No matches for '#{FindMode.query.rawQuery}'", 1000)
- return
- # The find was successfull. If we're in caret mode, then we should now have a selection, so we can
- # drop back into visual mode.
- @changeMode VisualMode if @name == "caret" and 0 < @selection.toString().length
-
- @movements.n = (count) -> doFind count, false
- @movements.N = (count) -> doFind count, true
- @movements["/"] = ->
- @findMode = new FindMode returnToViewport: true
- @findMode.onExit => @changeMode VisualMode
+ # Install basic bindings for find mode, "n" and "N".
+ do =>
+ doFind = (count, backwards) =>
+ initialRange = @selection.getRangeAt(0).cloneRange()
+ for [0...count] by 1
+ unless FindMode.execute null, {colorSelection: false, backwards}
+ @setSelectionRange initialRange
+ HUD.showForDuration("No matches for '#{FindMode.query.rawQuery}'", 1000)
+ return
+ # The find was successfull. If we're in caret mode, then we should now have a selection, so we can
+ # drop back into visual mode.
+ @changeMode VisualMode if @name == "caret" and 0 < @selection.toString().length
+
+ @movements.n = (count) -> doFind count, false
+ @movements.N = (count) -> doFind count, true
+ @movements["/"] = ->
+ @findMode = new FindMode returnToViewport: true
+ @findMode.onExit => @changeMode VisualMode
#
# End of Movement constructor.
@@ -374,41 +342,18 @@ class Movement extends CountPrefix
# it.
yank: (args = {}) ->
@yankedText = @selection.toString()
- if @options.deleteFromDocument or args.deleteFromDocument
- @selection.deleteFromDocument()
- else
- @selection.collapseToStart()
+ @selection.collapseToStart()
message = @yankedText.replace /\s+/g, " "
message = message[...12] + "..." if 15 < @yankedText.length
plural = if @yankedText.length == 1 then "" else "s"
- @options.onYank?.call @, @yankedText
@exit()
HUD.showForDuration "Yanked #{@yankedText.length} character#{plural}: \"#{message}\".", 2500
@yankedText
exit: (event, target) ->
- unless @options.parentMode or @options.oneMovementOnly
- @selection.collapseToStart() if event?.type == "keydown" and KeyboardUtils.isEscape event
-
- # Disabled, pending discussion of fine-tuning the UX. Simpler alternative is implemented above.
- # # If we're exiting on escape and there is a range selection, then we leave it in place. However, an
- # # immediately-following Escape clears the selection. See #1441.
- # if @selection.type == "Range" and event?.type == "keydown" and KeyboardUtils.isEscape event
- # handlerStack.push
- # _name: "visual/range/escape"
- # click: -> handlerStack.remove(); @continueBubbling
- # focus: -> handlerStack.remove(); @continueBubbling
- # keydown: (event) =>
- # handlerStack.remove()
- # if @selection.type == "Range" and event.type == "keydown" and KeyboardUtils.isEscape event
- # @collapseSelectionToFocus()
- # DomUtils.suppressKeyupAfterEscape handlerStack
- # @suppressEvent
- # else
- # @continueBubbling
-
+ @selection.collapseToStart() if event?.type == "keydown" and KeyboardUtils.isEscape event
super event, target
# For "daw", "das", and so on. We select a lexical entity (a word, a sentence or a paragraph).
@@ -450,11 +395,7 @@ class Movement extends CountPrefix
if @element and DomUtils.isEditable @element
if @element.clientHeight < @element.scrollHeight
if @element.isContentEditable
- # WIP (edit mode only)...
elementWithFocus = DomUtils.getElementWithFocus @selection, @getDirection() == backward
- # position = @element.getClientRects()[0].top - elementWithFocus.getClientRects()[0].top
- # console.log "top", position
- # Scroller.scrollToPosition @element, position, 0
position = elementWithFocus.getClientRects()[0].bottom - @element.getClientRects()[0].top - @element.clientHeight + @element.scrollTop
Scroller.scrollToPosition @element, position, 0
else
@@ -478,29 +419,24 @@ class VisualMode extends Movement
super extend defaults, options
# Establish or use the initial selection. If that's not possible, then enter caret mode.
- unless @options.oneMovementOnly or options.immediateMovement
- if @options.parentMode and @selection.type == "Caret"
- # We're being called from edit mode, so establish an intial visible selection.
- @extendByOneCharacter(forward) or @extendByOneCharacter backward
+ if @selection.type in [ "Caret", "Range" ]
+ selectionRect = @selection.getRangeAt(0).getBoundingClientRect()
+ selectionRect = Rect.intersect selectionRect, (Rect.create 0, 0, window.innerWidth,
+ window.innerHeight)
+ if selectionRect.height >= 0 and selectionRect.width >= 0
+ # The selection is visible in the current viewport.
+ if @selection.type == "Caret"
+ # The caret is in the viewport. Make make it visible.
+ @extendByOneCharacter(forward) or @extendByOneCharacter backward
else
- if @selection.type in [ "Caret", "Range" ]
- selectionRect = @selection.getRangeAt(0).getBoundingClientRect()
- selectionRect = Rect.intersect selectionRect, (Rect.create 0, 0, window.innerWidth,
- window.innerHeight)
- if selectionRect.height >= 0 and selectionRect.width >= 0
- # The selection is visible in the current viewport.
- if @selection.type == "Caret"
- # The caret is in the viewport. Make make it visible.
- @extendByOneCharacter(forward) or @extendByOneCharacter backward
- else
- # The selection is outside of the viewport: clear it. We guess that the user has moved on, and is
- # more likely to be interested in visible content.
- @selection.removeAllRanges()
+ # The selection is outside of the viewport: clear it. We guess that the user has moved on, and is
+ # more likely to be interested in visible content.
+ @selection.removeAllRanges()
- if @selection.type != "Range"
- @changeMode CaretMode
- HUD.showForDuration "No usable selection, entering caret mode...", 2500
- return
+ if @selection.type != "Range"
+ @changeMode CaretMode
+ HUD.showForDuration "No usable selection, entering caret mode...", 2500
+ return
@push
_name: "#{@id}/enter/click"
@@ -514,60 +450,29 @@ class VisualMode extends Movement
# Click in a focusable element exits.
click: (event) =>
@alwaysContinueBubbling =>
- unless @options.parentMode
- @exit event, event.target if DomUtils.isFocusable event.target
+ @exit event, event.target if DomUtils.isFocusable event.target
# Visual-mode commands.
- unless @options.oneMovementOnly
- @commands.y = -> @yank()
- @commands.p = -> chrome.runtime.sendMessage handler: "openUrlInCurrentTab", url: @yank()
- @commands.P = -> chrome.runtime.sendMessage handler: "openUrlInNewTab", url: @yank()
- @commands.V = -> @changeMode VisualLineMode
- @commands.c = -> @collapseSelectionToFocus(); @changeMode CaretMode
- @commands.o = -> @reverseSelection()
-
- # Additional commands when run under edit mode.
- if @options.parentMode
- @commands.x = -> @yank deleteFromDocument: true
- @commands.d = -> @yank deleteFromDocument: true
- @commands.c = -> @yank deleteFromDocument: true; @options.parentMode.enterInsertMode()
-
- # For edit mode's "yy" and "dd".
- if @options.yankLineCharacter
- @commands[@options.yankLineCharacter] = (count) ->
- @selectLine count; @yank()
-
- # For edit mode's "daw", "cas", and so on.
- if @options.oneMovementOnly
- @commands.a = (count) ->
- for entity in [ word, sentence, paragraph ]
- do (entity) =>
- @commands[entity.charAt 0] = ->
- @selectLexicalEntity entity, count; @yank()
+ @commands.y = -> @yank()
+ @commands.p = -> chrome.runtime.sendMessage handler: "openUrlInCurrentTab", url: @yank()
+ @commands.P = -> chrome.runtime.sendMessage handler: "openUrlInNewTab", url: @yank()
+ @commands.V = -> @changeMode VisualLineMode
+ @commands.c = -> @collapseSelectionToFocus(); @changeMode CaretMode
+ @commands.o = -> @reverseSelection()
+
#
# End of VisualMode constructor.
exit: (event, target) ->
- 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
- document.activeElement.blur() unless event?.type == "click"
-
- if @options.parentMode
- # E.g. when exiting visual mode under edit mode, we no longer want the selection.
- @collapseSelectionToFocus()
+ # 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
+ document.activeElement.blur() unless event?.type == "click"
super event, target
if @yankedText?
- unless @options.noCopyToClipboard
- console.log "yank:", @yankedText if @debug
- @copy @yankedText, true
-
- # Call sub-class; then yank, if we've only been created for a single movement.
- handleMovementKeyChar: (args...) ->
- super args...
- @yank() if @options.oneMovementOnly or @options.immediateMovement
+ console.log "yank:", @yankedText if @debug
+ @copy @yankedText, true
selectLine: (count) ->
@reverseSelection() if @getDirection() == forward
@@ -657,160 +562,6 @@ class CaretMode extends Movement
return true
false
-class EditMode extends Movement
- constructor: (options = {}) ->
- @alterMethod = "move"
- @element = document.activeElement
- return unless @element and DomUtils.isEditable @element
-
- options.indicator = "Edit mode"
- defaults =
- name: "edit"
- exitOnEscape: true
- exitOnBlur: @element
- super extend defaults, options
-
- # Edit mode commands.
- extend @commands,
- i: -> @enterInsertMode()
- a: -> @enterInsertMode()
- I: -> @runMovement backward, lineboundary; @enterInsertMode()
- A: -> @runMovement forward, lineboundary; @enterInsertMode()
- o: -> @openLine forward
- O: -> @openLine backward
- p: -> @pasteClipboard forward
- P: -> @pasteClipboard backward
- v: -> @launchSubMode VisualMode
- V: -> @launchSubMode VisualLineMode
-
- Y: (count) -> @enterVisualModeForMovement count, immediateMovement: "Y"
- x: (count) -> @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true, noCopyToClipboard: true
- X: (count) -> @enterVisualModeForMovement count, immediateMovement: "h", deleteFromDocument: true, noCopyToClipboard: true
- y: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "y"
- d: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "d", deleteFromDocument: true
- c: (count) -> @enterVisualModeForMovement count, deleteFromDocument: true, onYank: => @enterInsertMode()
-
- D: (count) -> @enterVisualModeForMovement 1, immediateMovement: "$", deleteFromDocument: true
- C: (count) -> @enterVisualModeForMovement 1, immediateMovement: "$", deleteFromDocument: true, onYank: => @enterInsertMode()
-
- '~': (count) -> @swapCase count, true
- 'g~': (count) -> @swapCase count, false
-
- # Disabled. Doesn't work reliably.
- # J: (count) ->
- # for [0...count]
- # @runMovement forward, lineboundary
- # @enterVisualModeForMovement 1, immediateMovement: "w", deleteFromDocument: true, noCopyToClipboard: true
- # DomUtils.simulateTextEntry @element, " "
-
- r: (count) ->
- handlerStack.push
- _name: "repeat-character"
- keydown: (event) => DomUtils.suppressPropagation event; @stopBubblingAndFalse
- keypress: (event) =>
- handlerStack.remove()
- keyChar = String.fromCharCode event.charCode
- if keyChar.length == 1
- @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true, noCopyToClipboard: true
- DomUtils.simulateTextEntry @element, [0...count].map(-> keyChar).join ""
- @suppressEvent
-
- # Disabled: potentially confusing.
- # # If the input is empty, then enter insert mode immediately.
- # unless @element.isContentEditable
- # if @element.value.trim() == ""
- # @enterInsertMode()
- # HUD.showForDuration "Input empty, entered insert mode directly.", 3500
- #
- # End of edit-mode constructor.
-
- # For "~", "3~", "g~3w", "g~e", and so on.
- swapCase: (count, immediate) ->
- @enterVisualModeForMovement count,
- immediateMovement: if immediate then "l" else null
- deleteFromDocument: true
- noCopyToClipboard: true
- onYank: (text) =>
- chars =
- for char in text.split ""
- if char == char.toLowerCase() then char.toUpperCase() else char.toLowerCase()
- DomUtils.simulateTextEntry @element, chars.join ""
-
- # For "p" and "P".
- pasteClipboard: (direction) ->
- @paste (text) =>
- if text
- # We use the following heuristic: if the text ends with a newline character, then it's a line-oriented
- # paste, and should be pasted in at a line break.
- if /\n$/.test text
- @runMovement backward, lineboundary
- @runMovement forward, line if direction == forward
- DomUtils.simulateTextEntry @element, text
- @runMovement backward, line
- else
- DomUtils.simulateTextEntry @element, text
-
- # For "o" and "O".
- openLine: (direction) ->
- @runMovement direction, lineboundary
- DomUtils.simulateTextEntry @element, "\n"
- @runMovement backward, character if direction == backward
- @enterInsertMode()
-
- # This lanches a visual-mode instance for one movement only, (usually) yanks the resulting selected text,
- # and (possibly) deletes it.
- enterVisualModeForMovement: (count, options = {}) ->
- @launchSubMode VisualMode, extend options,
- initialCountPrefix: count
- oneMovementOnly: true
-
- enterInsertMode: () ->
- @launchSubMode InsertMode,
- exitOnEscape: true
- targetElement: @options.targetElement
-
- launchSubMode: (mode, options = {}) ->
- @activeSubMode?.instance.exit()
- @activeSubMode =
- mode: mode
- options: options
- instance: new mode extend options, parentMode: @
- @activeSubMode.instance.onExit => @activeSubMode = null
-
- exit: (event, target) ->
- super event, target
-
- # 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
- @element.blur()
-
- if event?.type == "blur"
- # This instance of edit mode has now been entirely removed from the handler stack. It is inactive.
- # However, the user hasn't asked to leave edit mode, and may return. For example, we get a blur event
- # when we change tab. Or, the user may be copying text with the mouse. When the user does return,
- # they expect to still be in edit mode. We leave behind a "suspended-edit" mode which watches for focus
- # events and activates a new edit-mode instance if required.
- #
- # How does this get cleaned up? It's a bit tricky. The suspended-edit mode remains active on the
- # current input element indefinitely. However, the only way to enter edit mode is via focusInput. And
- # all modes launched by focusInput on a particular input element share a singleton (the element itself).
- # In addition, the new mode below shares the same singleton. So any new insert-mode or edit-mode
- # instance on this target element (the singleton) displaces any previously-active mode (including any
- # suspended-edit mode). PostFindMode shares the same singleton.
- #
- (new Mode name: "#{@id}-suspended", singleton: @options.singleton).push
- _name: "suspended-edit/#{@id}/focus"
- focus: (event) =>
- @alwaysContinueBubbling =>
- if event?.target == @options.targetElement
- editMode = new EditMode Utils.copyObjectOmittingProperties @options, "keydown", "keypress", "keyup"
- editMode.launchSubMode activeSubMode.mode, activeSubMode.options if activeSubMode
-
root = exports ? window
root.VisualMode = VisualMode
root.VisualLineMode = VisualLineMode
-root.EditMode = EditMode
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index 781223b1..e041245d 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -217,7 +217,6 @@ window.addEventListener "hashchange", onFocus
initializeOnDomReady = ->
# Tell the background page we're in the dom ready state.
chrome.runtime.connect({ name: "domReady" })
- CursorHider.init()
# We only initialize the vomnibar in the tab's main frame, because it's only ever opened there.
Vomnibar.init() if DomUtils.isTopFrame()
HUD.init()
@@ -836,40 +835,6 @@ toggleHelpDialog = (html, fid) ->
else
showHelpDialog(html, fid)
-CursorHider =
- #
- # Hide the cursor when the browser scrolls, and prevent mouse from hovering while invisible.
- #
- cursorHideStyle: null
- isScrolling: false
-
- onScroll: (event) ->
- CursorHider.isScrolling = true
- unless CursorHider.cursorHideStyle.parentElement
- document.head.appendChild CursorHider.cursorHideStyle
-
- onMouseMove: (event) ->
- if CursorHider.cursorHideStyle.parentElement and not CursorHider.isScrolling
- CursorHider.cursorHideStyle.remove()
- CursorHider.isScrolling = false
-
- init: ->
- # Temporarily disabled pending consideration of #1359 (in particular, whether cursor hiding is too fragile
- # as to provide a consistent UX).
- return
-
- # Disable cursor hiding for Chrome versions less than 39.0.2171.71 due to a suspected browser error.
- # See #1345 and #1348.
- return unless Utils.haveChromeVersion "39.0.2171.71"
-
- @cursorHideStyle = DomUtils.createElement "style"
- @cursorHideStyle.innerHTML = """
- body * {pointer-events: none !important; cursor: none !important;}
- body, html {cursor: none !important;}
- """
- window.addEventListener "mousemove", @onMouseMove
- window.addEventListener "scroll", @onScroll
-
initializePreDomReady()
DomUtils.documentReady initializeOnDomReady
DomUtils.documentReady registerFrame
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee
index 027188bf..7473df17 100644
--- a/lib/dom_utils.coffee
+++ b/lib/dom_utils.coffee
@@ -234,13 +234,27 @@ DomUtils =
if element.selectionStart == 0 and element.selectionEnd == 0
element.setSelectionRange element.value.length, element.value.length
-
+ simulateUnhover: (element, modifiers) -> @simulateMouseEvent "mouseout", element, modifiers
simulateClick: (element, modifiers) ->
- modifiers ||= {}
-
eventSequence = ["mouseover", "mousedown", "mouseup", "click"]
for event in eventSequence
+ @simulateMouseEvent event, element, modifiers
+
+ simulateMouseEvent: do ->
+ lastHoveredElement = undefined
+ (event, element, modifiers = {}) ->
+
+ if event == "mouseout"
+ element ?= lastHoveredElement # Allow unhovering the last hovered element by passing undefined.
+ lastHoveredElement = undefined
+ return unless element?
+
+ else if event == "mouseover"
+ # Simulate moving the mouse off the previous element first, as if we were a real mouse.
+ @simulateMouseEvent "mouseout", undefined, modifiers
+ lastHoveredElement = element
+
mouseEvent = document.createEvent("MouseEvents")
mouseEvent.initMouseEvent(event, true, true, window, 1, 0, 0, 0, 0, modifiers.ctrlKey, modifiers.altKey,
modifiers.shiftKey, modifiers.metaKey, 0, null)
diff --git a/lib/keyboard_utils.coffee b/lib/keyboard_utils.coffee
index 5c95680c..f123f75a 100644
--- a/lib/keyboard_utils.coffee
+++ b/lib/keyboard_utils.coffee
@@ -53,7 +53,7 @@ KeyboardUtils =
isEscape: (event) ->
# c-[ is mapped to ESC in Vim by default.
- (event.keyCode == @keyCodes.ESC) || (event.ctrlKey && @getKeyChar(event) == '[')
+ (event.keyCode == @keyCodes.ESC) || (event.ctrlKey && @getKeyChar(event) == '[' and not event.metaKey)
# TODO. This is probably a poor way of detecting printable characters. However, it shouldn't incorrectly
# identify any of chrome's own keyboard shortcuts as printable.
diff --git a/manifest.json b/manifest.json
index 63d96e6f..7fe13b13 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Vimium",
- "version": "1.53",
+ "version": "1.54",
"description": "The Hacker's Browser. Vimium provides keyboard shortcuts for navigation and control in the spirit of Vim.",
"icons": { "16": "icons/icon16.png",
"48": "icons/icon48.png",