diff options
| author | Stephen Blott | 2015-01-22 16:50:58 +0000 |
|---|---|---|
| committer | Stephen Blott | 2015-01-23 09:53:49 +0000 |
| commit | 256beee031efef70f4ee750044d9e697d66868bd (patch) | |
| tree | 851b8aa8abdb3b5875d5caa52166d83f13ca89b8 /content_scripts | |
| parent | eefe8c29b2410119412984301eba8c66dffda059 (diff) | |
| download | vimium-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.coffee | 6 | ||||
| -rw-r--r-- | content_scripts/mode_visual_edit.coffee | 136 | ||||
| -rw-r--r-- | content_scripts/scroller.coffee | 13 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 3 |
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" |
