diff options
| author | Stephen Blott | 2015-01-28 05:02:29 +0000 |
|---|---|---|
| committer | Stephen Blott | 2015-01-28 09:11:03 +0000 |
| commit | c117ac90faf8c685b40195ac54b237dfcb2b648b (patch) | |
| tree | 50382db21be10998e8ea8d804acfe79d52feb98c | |
| parent | a982ce074d517eb9e56c517d86a4bfb3869cf171 (diff) | |
| download | vimium-c117ac90faf8c685b40195ac54b237dfcb2b648b.tar.bz2 | |
Visual/edit modes: minor changes...
- Minor refactoring.
- Better selection of entity for "daw" an friends.
- dd uses count, and works for empty lines.
- Count for daw, etc.
- Fix bug whereby selection cleared when changing tabs.
| -rw-r--r-- | content_scripts/mode.coffee | 1 | ||||
| -rw-r--r-- | content_scripts/mode_visual_edit.coffee | 128 |
2 files changed, 66 insertions, 63 deletions
diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee index c2e70e91..e7a4e0ee 100644 --- a/content_scripts/mode.coffee +++ b/content_scripts/mode.coffee @@ -91,7 +91,6 @@ class Mode # be unique. New instances deactivate existing instances with the same key. if @options.singleton do => - console.log @options.singleton @deactivateSingleton @options.singleton @onExit => Mode.singletons = Mode.singletons.filter (active) => active.key != @options.singleton Mode.singletons.push key: @options.singleton, mode: @ diff --git a/content_scripts/mode_visual_edit.coffee b/content_scripts/mode_visual_edit.coffee index 70d95dff..d7188c1f 100644 --- a/content_scripts/mode_visual_edit.coffee +++ b/content_scripts/mode_visual_edit.coffee @@ -3,32 +3,27 @@ # Konami code? # Use find as a mode. # Perhaps refactor visual/movement modes. -# FocusInput selector is currently broken. +# Exit on Ctrl-Enter. +# Scroll is broken (again). Seems to be after dd. # This prevents printable characters from being passed through to the underlying page. It should, however, # allow through Chrome keyboard shortcuts. It's a keyboard-event backstop for visual mode and edit mode. class SuppressPrintable extends Mode constructor: (options = {}) -> handler = (event) => - if KeyboardUtils.isPrintable event - if event.type == "keydown" - # Completely suppress Backspace and Delete. - if event.keyCode in [ 8, 46 ] - @suppressEvent - else - DomUtils.suppressPropagation - @stopBubblingAndFalse - else - @suppressEvent - else - @stopBubblingAndTrue + return @stopBubblingAndTrue unless KeyboardUtils.isPrintable event + return @suppressEvent unless event.type == "keydown" + # Completely suppress Backspace and Delete. + return @suppressEvent if event.keyCode in [ 8, 46 ] + DomUtils.suppressPropagation event + @stopBubblingAndFalse super extend options, keydown: handler keypress: handler keyup: handler -# This watches keyboard events and maintains @countPrefix as number keys and other keys are pressed. +# This watches keypresses and maintains the count prefix as number keys and other keys are pressed. class CountPrefix extends SuppressPrintable constructor: (options) -> super options @@ -57,13 +52,12 @@ class CountPrefix extends SuppressPrintable @countPrefixFactor = 1 count -# Some symbolic names for widely-used strings. +# Some symbolic names for frequently-used strings. forward = "forward" backward = "backward" character = "character" -# This implements movement commands with count prefixes (using CountPrefix) for both visual mode and edit -# mode. +# This implements movement commands with count prefixes for both visual mode and edit mode. class Movement extends CountPrefix opposite: forward: backward, backward: forward @@ -73,8 +67,7 @@ class Movement extends CountPrefix paste: (callback) -> chrome.runtime.sendMessage handler: "pasteFromClipboard", (response) -> callback response - # Return a value which changes whenever the selection changes, regardless of whether the selection is - # collapsed. + # Return a value which changes whenever the selection changes. hashSelection: -> [ @element?.selectionStart, @selection.toString().length ].join "/" @@ -82,10 +75,10 @@ class Movement extends CountPrefix selectionChanged: (func) -> before = @hashSelection(); func(); @hashSelection() != before - # Run a movement. The arguments can be one of the following forms: - # - "forward word" (one argument, a string) - # - [ "forward", "word" ] (one argument, not a string) - # - "forward", "word" (two arguments) + # Run a movement. For convenience, the following three forms can be used: + # @runMovement "forward word" + # @runMovement [ "forward", "word" ] + # @runMovement "forward", "word" runMovement: (args...) -> movement = if typeof(args[0]) == "string" and args.length == 1 @@ -103,16 +96,15 @@ class Movement extends CountPrefix # Swap the anchor node/offset and the focus node/offset. reverseSelection: -> element = document.activeElement + direction = @getDirection() if element and DomUtils.isEditable(element) and not element.isContentEditable # Note(smblott). This implementation is unacceptably inefficient if the selection is large. We only use - # it if we have to. However, the normal method (below) does not work for input elements. - direction = @getDirection() + # it if we have to. However, the normal method (below) does not work for simple text inputs. length = @selection.toString().length @collapseSelectionToFocus() @runMovement @opposite[direction], character for [0...length] else # Normal method. - direction = @getDirection() original = @selection.getRangeAt(0).cloneRange() range = original.cloneRange() range.collapse direction == backward @@ -138,12 +130,21 @@ class Movement extends CountPrefix return if 0 < change then direction else @opposite[direction] forward - # An approximation of the vim "w" movement; only ever used in the forward direction. The last two character - # movements allow us to also get to the end of the very-last word. - moveForwardWord: () -> + # An approximation of the vim "w" movement; only ever used in the forward direction. + moveForwardWord: -> + # 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" + return + # Previous version... + # This works in normal text inputs, but not in some contentEditable elements (notably the compose window + # on Google's Inbox). # First, move to the end of the preceding word... if @runMovements "forward character", "backward word", "forward word" # And then to the start of the following word... + # The two character movements allow us to also get to the end of the very-last word. @runMovements "forward word", "forward character", "backward character", "backward word" collapseSelectionToAnchor: -> @@ -173,8 +174,8 @@ class Movement extends CountPrefix "0": "backward lineboundary" "G": "forward documentboundary" "g": "backward documentboundary" - "w": -> @moveForwardWord() "Y": -> @selectLexicalEntity "lineboundary" + "w": -> @moveForwardWord() "o": -> @reverseSelection() constructor: (options) -> @@ -230,11 +231,14 @@ class Movement extends CountPrefix action() for [0...count] @scrollIntoView() - # Yank the selection; always exits; returns the yanked text. + # Yank the selection; always exits; either deletes the selection or collapses it; returns the yanked text. yank: (args = {}) -> @yankedText = @selection.toString() - @selection.deleteFromDocument() if args.deleteFromDocument or @options.deleteFromDocument console.log "yank:", @yankedText if @debug + if args.deleteFromDocument or @options.deleteFromDocument + @selection.deleteFromDocument() + else + @collapseSelectionToAnchor() message = @yankedText.replace /\s+/g, " " length = @yankedText.length @@ -246,14 +250,18 @@ class Movement extends CountPrefix @exit() @yankedText - # Select a lexical entity, such as a word, a line, or a sentence. The entity should be a Chrome movement - # type, such as "word" or "lineboundary". This assumes that the selection is initially collapsed. - selectLexicalEntity: (entity) -> + # Select a lexical entity, such as a word, or a sentence. The entity should be a Chrome movement type, such + # as "word" or "lineboundary". + selectLexicalEntity: (entity, count = 1) -> + # Locate the start of the current entity. + @runMovement forward, entity + @runMovement backward, entity + @collapseSelectionToFocus() if @options.oneMovementOnly + for [0...count] + @runMovement forward, entity + @runMovement forward, character @runMovement forward, entity - @selection.collapseToEnd() @runMovement backward, entity - # Move the end of the preceding entity. - @runMovements [ backward, entity ], [ forward, entity ] # Try to scroll the focus into view. scrollIntoView: -> @@ -325,20 +333,22 @@ class VisualMode extends Movement # For "yy" and "dd". if @options.yankLineCharacter - @commands[@options.yankLineCharacter] = -> + @commands[@options.yankLineCharacter] = (count) -> if @keypressCount == 1 - @selectLexicalEntity "lineboundary" + @selectLexicalEntity "lineboundary", count @yank() # For "daw", "cas", and so on. if @options.oneMovementOnly - @commands.a = -> + @commands.a = (count) -> if @keypressCount == 1 + # We do not include "paragraph", here. Chrome's paragraph movements seem to be asymmetrical, + # meaning "dap" ends up deleting the wrong text. for entity in [ "word", "sentence", "paragraph" ] do (entity) => - @movements[entity.charAt 0] = -> + @commands[entity.charAt 0] = -> if @keypressCount == 2 - @selectLexicalEntity entity + @selectLexicalEntity entity, count @yank() unless @options.editModeParent @@ -361,8 +371,6 @@ class VisualMode extends Movement super @clipboardContents = text exit: (event, target) -> - @collapseSelectionToAnchor() if @yankedText or @options.editModeParent - unless @options.editModeParent # Don't leave the user in insert mode just because they happen to have selected text within an input # element. @@ -464,15 +472,15 @@ class EditMode extends Movement P: -> @pasteClipboard backward v: -> @launchSubMode VisualMode - Y: -> @enterVisualModeForMovement immediateMovement: "Y" - x: -> @enterVisualModeForMovement immediateMovement: "h", deleteFromDocument: true - X: -> @enterVisualModeForMovement immediateMovement: "l", deleteFromDocument: true - y: -> @enterVisualModeForMovement yankLineCharacter: "y" - d: -> @enterVisualModeForMovement yankLineCharacter: "d", deleteFromDocument: true - c: -> @enterVisualModeForMovement deleteFromDocument: true, onYank: => @enterInsertMode() + Y: (count) -> @enterVisualModeForMovement 1, immediateMovement: "Y" + x: (count) -> @enterVisualModeForMovement count, immediateMovement: "h", deleteFromDocument: true + X: (count) -> @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true + y: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "y" + d: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "d", deleteFromDocument: true + c: (count) -> @enterVisualModeForMovement count, deleteFromDocument: true, onYank: => @enterInsertMode() - D: -> @enterVisualModeForMovement immediateMovement: "$", deleteFromDocument: true - C: -> @enterVisualModeForMovement immediateMovement: "$", deleteFromDocument: true, onYank: => @enterInsertMode() + D: (count) -> @enterVisualModeForMovement 1, immediateMovement: "$", deleteFromDocument: true + C: (count) -> @enterVisualModeForMovement 1, immediateMovement: "$", deleteFromDocument: true, onYank: => @enterInsertMode() # Disabled as potentially confusing. # # If the input is empty, then enter insert mode immediately. @@ -481,10 +489,10 @@ class EditMode extends Movement # @enterInsertMode() # HUD.showForDuration "Input empty, entered insert mode directly.", 3500 - enterVisualModeForMovement: (options = {}) -> + enterVisualModeForMovement: (count, options = {}) -> @launchSubMode VisualMode, extend options, badge: "M" - initialCountPrefix: @getCountPrefix() + initialCountPrefix: count oneMovementOnly: true enterInsertMode: () -> @@ -538,23 +546,19 @@ class EditMode extends Movement if event?.type == "blur" # This instance of edit mode has now been entirely removed from the handler stack. It is inactive. - # However, the user may return. For example, we get a blur event when we change tabs. Or, the user may + # However, the user 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 this gets cleaned up is a bit tricky. The suspended-edit mode remains active on the current input - # element indefinately. However, the only way to enter edit mode is via focusInput. And all modes + # 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 a newly-activated 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. # - suspendedEditmode = new Mode - name: "#{@id}-suspended" - singleton: @options.singleton - - suspendedEditmode.push + (new Mode name: "#{@id}-suspended", singleton: @options.singleton).push _name: "suspended-edit/#{@id}/focus" focus: (event) => @alwaysContinueBubbling => |
