aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts
diff options
context:
space:
mode:
authorStephen Blott2015-01-22 16:50:58 +0000
committerStephen Blott2015-01-23 09:53:49 +0000
commit256beee031efef70f4ee750044d9e697d66868bd (patch)
tree851b8aa8abdb3b5875d5caa52166d83f13ca89b8 /content_scripts
parenteefe8c29b2410119412984301eba8c66dffda059 (diff)
downloadvimium-256beee031efef70f4ee750044d9e697d66868bd.tar.bz2
Visual/edit modes: develop edit mode.
- implement "i", "a". - fix "w" for edit mode. - try out "e" for enter edit mode. - initial implementation "o", "O" - Suppress backspace and delete. - Scroll in text areas.
Diffstat (limited to 'content_scripts')
-rw-r--r--content_scripts/mode_insert.coffee6
-rw-r--r--content_scripts/mode_visual_edit.coffee136
-rw-r--r--content_scripts/scroller.coffee13
-rw-r--r--content_scripts/vimium_frontend.coffee3
4 files changed, 91 insertions, 67 deletions
diff --git a/content_scripts/mode_insert.coffee b/content_scripts/mode_insert.coffee
index eac4a3d0..818c8408 100644
--- a/content_scripts/mode_insert.coffee
+++ b/content_scripts/mode_insert.coffee
@@ -10,10 +10,11 @@ class InsertMode extends Mode
handleKeyEvent = (event) =>
return @continueBubbling unless @isActive event
+ console.log "key", String.fromCharCode(event.charCode) if event.type == 'keypress'
return @stopBubblingAndTrue unless event.type == 'keydown' and KeyboardUtils.isEscape event
DomUtils.suppressKeyupAfterEscape handlerStack
target = event.srcElement
- if target and DomUtils.isFocusable target
+ if target and DomUtils.isFocusable(target) and @options.blurOnEscape
# Remove the focus, so the user can't just get back into insert mode by typing in the same input box.
# NOTE(smblott, 2014/12/22) Including embeds for .blur() etc. here is experimental. It appears to be
# the right thing to do for most common use cases. However, it could also cripple flash-based sites and
@@ -27,6 +28,7 @@ class InsertMode extends Mode
keypress: handleKeyEvent
keyup: handleKeyEvent
keydown: handleKeyEvent
+ blurOnEscape: true
super extend defaults, options
@@ -38,6 +40,7 @@ class InsertMode extends Mode
null
@push
+ _name: "mode-#{@id}-focus"
"blur": (event) => @alwaysContinueBubbling =>
target = event.target
# We can't rely on focus and blur events arriving in the expected order. When the active element
@@ -74,6 +77,7 @@ class InsertMode extends Mode
if @permanent then Mode.updateBadge() else super()
updateBadge: (badge) ->
+ badge.badge ||= @badge if @badge
badge.badge ||= "I" if @isActive badge
# Static stuff. This allows PostFindMode to suppress the permanently-installed InsertMode instance.
diff --git a/content_scripts/mode_visual_edit.coffee b/content_scripts/mode_visual_edit.coffee
index 657ae677..e6ea968a 100644
--- a/content_scripts/mode_visual_edit.coffee
+++ b/content_scripts/mode_visual_edit.coffee
@@ -7,8 +7,12 @@ class SuppressPrintable extends Mode
handler = (event) =>
if KeyboardUtils.isPrintable event
if event.type == "keydown"
- DomUtils.suppressPropagation
- @stopBubblingAndFalse
+ # Completely suppress Backspace and Delete.
+ if event.keyCode in [ 8, 46 ]
+ @suppressEvent
+ else
+ DomUtils.suppressPropagation
+ @stopBubblingAndFalse
else
false
else
@@ -59,6 +63,13 @@ class Movement extends MaintainCount
forward: backward
backward: forward
+ # Call a function. Return true if the selection changed.
+ selectionChanged: (func) ->
+ r = @selection.getRangeAt(0).cloneRange()
+ func()
+ rr = @selection.getRangeAt(0)
+ not (r.compareBoundaryPoints(Range.END_TO_END, rr) and r.compareBoundaryPoints Range.START_TO_START, rr)
+
# Try to move one character in "direction". Return 1, -1 or 0, indicating that the selection got bigger, or
# smaller, or is unchanged.
moveInDirection: (direction) ->
@@ -84,19 +95,19 @@ class Movement extends MaintainCount
text.charAt if @getDirection() == forward then text.length - 1 else 0
moveByWord: (direction) ->
- # We go to the end of the next word, then come back to the start of it.
- movements = [ "#{direction} word", "#{@opposite[direction]} word" ]
- # If we're in the middle of a word, then we also need to skip over that one.
- movements.unshift "#{direction} word" unless /\s/.test @nextCharacter direction
- @runMovements movements
+ @runMovement "#{direction} word" unless /\s/.test @nextCharacter direction
+ while /\s/.test @nextCharacter direction
+ break unless @selectionChanged =>
+ @runMovement "#{direction} character"
- # Run a movement command. Return true if the length of the selection changed, false otherwise.
+ # Run a movement command.
runMovement: (movement) ->
length = @selection.toString().length
@selection.modify @alterMethod, movement.split(" ")...
- @selection.toString().length != length
+ @alterMethod == "move" or @selection.toString().length != length
runMovements: (movements) ->
+ console.log movements
for movement in movements
break unless @runMovement movement
@@ -107,6 +118,7 @@ class Movement extends MaintainCount
"k": "backward line"
"e": "forward word"
"b": "backward word"
+ "B": "backward word"
")": "forward sentence"
"(": "backward sentence"
"}": "forward paragraph"
@@ -115,44 +127,16 @@ class Movement extends MaintainCount
"0": "backward lineboundary"
"G": "forward documentboundary"
"g": "backward documentboundary"
-
"w": -> @moveByWord forward
- "W": -> @moveByWord backward
+ "W": -> @moveByWord forward
"o": ->
- # Swap the anchor and focus.
+ # Swap the anchor and focus. This is too slow if the selection is large.
+ direction = @getDirection()
length = @selection.toString().length
- switch @getDirection()
- when forward
- @selection.collapseToEnd()
- # FIXME(smblott). This is super slow if the selection is large.
- @selection.modify "extend", backward, character for [0...length]
- when backward
- @selection.collapseToStart()
- @selection.modify "extend", forward, character for [0...length]
- # Faster, but doesn't always work...
- # @selection.extend @selection.anchorNode, length
- return
- # Note(smblott). I can't find an efficient approach which works for all cases, so we have to implement
- # each case separately.
- # FIXME: This is broken if the selection is in an input area.
- original = @selection.getRangeAt 0
- switch @getDirection()
- when forward
- range = original.cloneRange()
- range.collapse false
- @selection.removeAllRanges()
- @selection.addRange range
- @selection.extend original.startContainer, original.startOffset
- when backward
- range = document.createRange()
- range.setStart @selection.focusNode, @selection.focusOffset
- range.setEnd @selection.anchorNode, @selection.anchorOffset
- @selection.removeAllRanges()
- @selection.addRange range
- return
+ @selection[if direction == forward then "collapseToEnd" else "collapseToStart"]()
+ @selection.modify "extend", @opposite[direction], character for [0...length]
- # TODO(smblott). What do we do if there is no initial selection? Or multiple ranges?
constructor: (options) ->
@alterMethod = options.alterMethod || "extend"
super options
@@ -171,10 +155,7 @@ class Movement extends MaintainCount
@runMovement @movements[keyChar]
when "function"
@movements[keyChar].call @
- # Try to scroll the leading end of the selection into view. getLeadingElement() seems to work
- # most, but not all, of the time.
- leadingElement = @getLeadingElement @selection
- Scroller.scrollIntoView leadingElement if leadingElement
+ @scrollIntoView()
# Adapted from: http://roysharon.com/blog/37.
# I have no idea how this works (smblott, 2015/1/22).
@@ -190,6 +171,21 @@ class Movement extends MaintainCount
t = o || t?.parentNode
t
+ # Try to scroll the leading end of the selection into view.
+ scrollIntoView: ->
+ if document.activeElement and DomUtils.isEditable document.activeElement
+ element = document.activeElement
+ if element.clientHeight < element.scrollHeight
+ if element.isContentEditable
+ # How do we do this?
+ else
+ coords = DomUtils.getCaretCoordinates element, element.selectionStart
+ Scroller.scrollToPosition element, coords.top, coords.left
+ else
+ # getLeadingElement() seems to work most, but not all, of the time.
+ leadingElement = @getLeadingElement @selection
+ Scroller.scrollIntoView leadingElement if leadingElement
+
class VisualMode extends Movement
constructor: (options = {}) ->
@selection = window.getSelection()
@@ -207,7 +203,6 @@ class VisualMode extends Movement
name: "visual"
badge: "V"
exitOnEscape: true
- exitOnBlur: options.targetElement
alterMethod: "extend"
keypress: (event) =>
@@ -220,16 +215,18 @@ class VisualMode extends Movement
handler: "copyToClipboard"
data: text
@exit()
- handlerStack.push keyup: => false
length = text.length
suffix = if length == 1 then "" else "s"
text = text[...12] + "..." if 15 < length
+ text = text.replace /\n/g, " "
HUD.showForDuration "Yanked #{length} character#{suffix}: \"#{text}\".", 2500
super extend defaults, options
@debug = true
- # FIXME(smblott).
+ # FIXME(smblott). We can't handle the selection changing with the mouse while while visual-mode is
+ # active. This "fix" doesn't work.
+ # work.
# onMouseUp = (event) =>
# @alwaysContinueBubbling =>
# if event.which == 1
@@ -249,33 +246,42 @@ class EditMode extends Movement
constructor: (options = {}) ->
defaults =
name: "edit"
+ badge: "E"
exitOnEscape: true
alterMethod: "move"
- keydown: (event) => if @isActive() then @handleKeydown event else @continueBubbling
- keypress: (event) => if @isActive() then @handleKeypress event else @continueBubbling
- keyup: (event) => if @isActive() then @handleKeyup event else @continueBubbling
+ @debug = true
@element = document.activeElement
- if @element and DomUtils.isEditable @element
- super extend defaults, options
-
- handleKeydown: (event) ->
- @stopBubblingAndTrue
- handleKeypress: (event) ->
- @suppressEvent
- handleKeyup: (event) ->
- @stopBubblingAndTrue
+ return unless @element and DomUtils.isEditable @element
+ super extend defaults, options
+ handlerStack.debug = true
- isActive: ->
- document.activeElement and DomUtils.isDOMDescendant @element, document.activeElement
+ extend @movements,
+ "i": => @enterInsertMode()
+ "a": => @enterInsertMode()
+ "o": => @openLine forward
+ "O": => @openLine backward
exit: (event, target) ->
super()
@element.blur() if target? and DomUtils.isDOMDescendant @element, target
EditMode.activeElements = EditMode.activeElements.filter (element) => element != @element
- updateBadge: (badge) ->
- badge.badge = "E" if @isActive()
+ enterInsertMode: ->
+ new InsertMode
+ badge: "I"
+ blurOnEscape: false
+
+ openLine: (direction) ->
+ @runMovement "#{direction} lineboundary"
+ @enterInsertMode()
+ @simulateTextEntry "\n"
+ @runMovement "backward character" if direction == backward
+
+ simulateTextEntry: (text) ->
+ event = document.createEvent "TextEvent"
+ event.initTextEvent "textInput", true, true, null, text
+ document.activeElement.dispatchEvent event
root = exports ? window
root.VisualMode = VisualMode
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index f31c4a6b..43fad87e 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -254,5 +254,18 @@ Scroller =
else if window.innerHeight < rect.bottom
CoreScroller.scroll activatedElement, "y", 50 + rect.bottom - window.innerHeight, false
+ scrollToPosition: (element, top, left) ->
+ padding = 20
+ bottom = top + padding
+ right = left + padding
+
+ element.scrollTop = top if top <= element.scrollTop
+ element.scrollLeft = left if left <= element.scrollLeft
+
+ if element.scrollTop + element.clientHeight <= bottom
+ element.scrollTop = bottom - element.clientHeight
+ if element.scrollLeft + element.clientWidth <= right
+ element.scrollLeft = right - element.clientWidth
+
root = exports ? window
root.Scroller = Scroller
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index 79302930..fefb64ba 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -397,7 +397,8 @@ extend window,
@suppressEvent
else unless event.keyCode == KeyboardUtils.keyCodes.shiftKey
@exit()
- @continueBubbling
+ # Give the new mode the opportunity to handle the event.
+ @restartBubbling
@hintContainingDiv = DomUtils.addElementList hints,
id: "vimiumInputMarkerContainer"