diff options
| author | Stephen Blott | 2016-01-31 12:43:10 +0000 |
|---|---|---|
| committer | Stephen Blott | 2016-01-31 12:43:10 +0000 |
| commit | 72f76a4ef22b06e076cf631b8e0c3ad5cc3ccfbd (patch) | |
| tree | 0e2766458226236f7f0cc54e8ba0ea58fc0ce45a | |
| parent | 1f8abb98a3750a00ee16aac34f81de27413f1816 (diff) | |
| parent | 68a39707817ab8693e1ddd9381611c0bfb46ebaa (diff) | |
| download | vimium-72f76a4ef22b06e076cf631b8e0c3ad5cc3ccfbd.tar.bz2 | |
Merge pull request #1961 from smblott-github/remove-edit-mode
Wholly remove edit-mode code.
| -rw-r--r-- | background_scripts/commands.coffee | 3 | ||||
| -rw-r--r-- | content_scripts/mode_visual_edit.coffee | 361 |
2 files changed, 56 insertions, 308 deletions
diff --git a/background_scripts/commands.coffee b/background_scripts/commands.coffee index c8121a96..e3a6f0dc 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/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 |
