]> git.r.bdr.sh - rbdr/dotfiles/blobdiff - atom/packages/vim-mode/lib/vim-state.coffee
Update vim plugins
[rbdr/dotfiles] / atom / packages / vim-mode / lib / vim-state.coffee
index a45551c3b4a13f317d93fdca5d4f8fa3e9a3ffb1..db59ee5156c32416385eda26140fb7f35f95db58 100644 (file)
@@ -1,3 +1,4 @@
+Grim  = require 'grim'
 _ = require 'underscore-plus'
 {Point, Range} = require 'atom'
 {Emitter, Disposable, CompositeDisposable} = require 'event-kit'
 _ = require 'underscore-plus'
 {Point, Range} = require 'atom'
 {Emitter, Disposable, CompositeDisposable} = require 'event-kit'
@@ -6,6 +7,7 @@ settings = require './settings'
 Operators = require './operators/index'
 Prefixes = require './prefixes'
 Motions = require './motions/index'
 Operators = require './operators/index'
 Prefixes = require './prefixes'
 Motions = require './motions/index'
+InsertMode = require './insert-mode'
 
 TextObjects = require './text-objects'
 Utils = require './utils'
 
 TextObjects = require './text-objects'
 Utils = require './utils'
@@ -18,6 +20,7 @@ class VimState
   mode: null
   submode: null
   destroyed: false
   mode: null
   submode: null
   destroyed: false
+  replaceModeListener: null
 
   constructor: (@editorElement, @statusBarManager, @globalVimState) ->
     @emitter = new Emitter
 
   constructor: (@editorElement, @statusBarManager, @globalVimState) ->
     @emitter = new Emitter
@@ -28,52 +31,56 @@ class VimState
     @marks = {}
     @subscriptions.add @editor.onDidDestroy => @destroy()
 
     @marks = {}
     @subscriptions.add @editor.onDidDestroy => @destroy()
 
-    @subscriptions.add @editor.onDidChangeSelectionRange _.debounce(=>
-      return unless @editor?
-      if @editor.getSelections().every((selection) -> selection.isEmpty())
-        @activateCommandMode() if @mode is 'visual'
-      else
-        @activateVisualMode('characterwise') if @mode is 'command'
-    , 100)
+    @editorElement.addEventListener 'mouseup', @checkSelections
+    if atom.commands.onDidDispatch?
+      @subscriptions.add atom.commands.onDidDispatch (e) =>
+        if e.target is @editorElement
+          @checkSelections()
 
     @editorElement.classList.add("vim-mode")
 
     @editorElement.classList.add("vim-mode")
-    @setupCommandMode()
+    @setupNormalMode()
     if settings.startInInsertMode()
       @activateInsertMode()
     else
     if settings.startInInsertMode()
       @activateInsertMode()
     else
-      @activateCommandMode()
+      @activateNormalMode()
 
   destroy: ->
     unless @destroyed
       @destroyed = true
 
   destroy: ->
     unless @destroyed
       @destroyed = true
-      @emitter.emit 'did-destroy'
       @subscriptions.dispose()
       if @editor.isAlive()
         @deactivateInsertMode()
         @editorElement.component?.setInputEnabled(true)
         @editorElement.classList.remove("vim-mode")
       @subscriptions.dispose()
       if @editor.isAlive()
         @deactivateInsertMode()
         @editorElement.component?.setInputEnabled(true)
         @editorElement.classList.remove("vim-mode")
-        @editorElement.classList.remove("command-mode")
+        @editorElement.classList.remove("normal-mode")
+      @editorElement.removeEventListener 'mouseup', @checkSelections
       @editor = null
       @editorElement = null
       @editor = null
       @editorElement = null
+      @emitter.emit 'did-destroy'
 
   # Private: Creates the plugin's bindings
   #
   # Returns nothing.
 
   # Private: Creates the plugin's bindings
   #
   # Returns nothing.
-  setupCommandMode: ->
+  setupNormalMode: ->
     @registerCommands
     @registerCommands
-      'activate-command-mode': => @activateCommandMode()
+      'activate-normal-mode': => @activateNormalMode()
       'activate-linewise-visual-mode': => @activateVisualMode('linewise')
       'activate-characterwise-visual-mode': => @activateVisualMode('characterwise')
       'activate-blockwise-visual-mode': => @activateVisualMode('blockwise')
       'activate-linewise-visual-mode': => @activateVisualMode('linewise')
       'activate-characterwise-visual-mode': => @activateVisualMode('characterwise')
       'activate-blockwise-visual-mode': => @activateVisualMode('blockwise')
-      'reset-command-mode': => @resetCommandMode()
+      'reset-normal-mode': => @resetNormalMode()
       'repeat-prefix': (e) => @repeatPrefix(e)
       'reverse-selections': (e) => @reverseSelections(e)
       'undo': (e) => @undo(e)
       'repeat-prefix': (e) => @repeatPrefix(e)
       'reverse-selections': (e) => @reverseSelections(e)
       'undo': (e) => @undo(e)
+      'replace-mode-backspace': => @replaceModeUndo()
+      'insert-mode-put': (e) => @insertRegister(@registerName(e))
+      'copy-from-line-above': => InsertMode.copyCharacterFromAbove(@editor, this)
+      'copy-from-line-below': => InsertMode.copyCharacterFromBelow(@editor, this)
 
     @registerOperationCommands
       'activate-insert-mode': => new Operators.Insert(@editor, this)
 
     @registerOperationCommands
       'activate-insert-mode': => new Operators.Insert(@editor, this)
-      'substitute': => new Operators.Substitute(@editor, this)
-      'substitute-line': => new Operators.SubstituteLine(@editor, this)
+      'activate-replace-mode': => new Operators.ReplaceMode(@editor, this)
+      'substitute': => [new Operators.Change(@editor, this), new Motions.MoveRight(@editor, this)]
+      'substitute-line': => [new Operators.Change(@editor, this), new Motions.MoveToRelativeLine(@editor, this)]
       'insert-after': => new Operators.InsertAfter(@editor, this)
       'insert-after-end-of-line': => new Operators.InsertAfterEndOfLine(@editor, this)
       'insert-at-beginning-of-line': => new Operators.InsertAtBeginningOfLine(@editor, this)
       'insert-after': => new Operators.InsertAfter(@editor, this)
       'insert-after-end-of-line': => new Operators.InsertAfterEndOfLine(@editor, this)
       'insert-at-beginning-of-line': => new Operators.InsertAtBeginningOfLine(@editor, this)
@@ -135,7 +142,10 @@ class VimState
       'scroll-full-screen-up': => new Motions.ScrollFullUpKeepCursor(@editorElement, this)
       'scroll-half-screen-down': => new Motions.ScrollHalfDownKeepCursor(@editorElement, this)
       'scroll-full-screen-down': => new Motions.ScrollFullDownKeepCursor(@editorElement, this)
       'scroll-full-screen-up': => new Motions.ScrollFullUpKeepCursor(@editorElement, this)
       'scroll-half-screen-down': => new Motions.ScrollHalfDownKeepCursor(@editorElement, this)
       'scroll-full-screen-down': => new Motions.ScrollFullDownKeepCursor(@editorElement, this)
+      'scroll-cursor-to-left': => new Scroll.ScrollCursorToLeft(@editorElement)
+      'scroll-cursor-to-right': => new Scroll.ScrollCursorToRight(@editorElement)
       'select-inside-word': => new TextObjects.SelectInsideWord(@editor)
       'select-inside-word': => new TextObjects.SelectInsideWord(@editor)
+      'select-inside-whole-word': => new TextObjects.SelectInsideWholeWord(@editor)
       'select-inside-double-quotes': => new TextObjects.SelectInsideQuotes(@editor, '"', false)
       'select-inside-single-quotes': => new TextObjects.SelectInsideQuotes(@editor, '\'', false)
       'select-inside-back-ticks': => new TextObjects.SelectInsideQuotes(@editor, '`', false)
       'select-inside-double-quotes': => new TextObjects.SelectInsideQuotes(@editor, '"', false)
       'select-inside-single-quotes': => new TextObjects.SelectInsideQuotes(@editor, '\'', false)
       'select-inside-back-ticks': => new TextObjects.SelectInsideQuotes(@editor, '`', false)
@@ -146,6 +156,7 @@ class VimState
       'select-inside-parentheses': => new TextObjects.SelectInsideBrackets(@editor, '(', ')', false)
       'select-inside-paragraph': => new TextObjects.SelectInsideParagraph(@editor, false)
       'select-a-word': => new TextObjects.SelectAWord(@editor)
       'select-inside-parentheses': => new TextObjects.SelectInsideBrackets(@editor, '(', ')', false)
       'select-inside-paragraph': => new TextObjects.SelectInsideParagraph(@editor, false)
       'select-a-word': => new TextObjects.SelectAWord(@editor)
+      'select-a-whole-word': => new TextObjects.SelectAWholeWord(@editor)
       'select-around-double-quotes': => new TextObjects.SelectInsideQuotes(@editor, '"', true)
       'select-around-single-quotes': => new TextObjects.SelectInsideQuotes(@editor, '\'', true)
       'select-around-back-ticks': => new TextObjects.SelectInsideQuotes(@editor, '`', true)
       'select-around-double-quotes': => new TextObjects.SelectInsideQuotes(@editor, '"', true)
       'select-around-single-quotes': => new TextObjects.SelectInsideQuotes(@editor, '\'', true)
       'select-around-back-ticks': => new TextObjects.SelectInsideQuotes(@editor, '`', true)
@@ -165,8 +176,8 @@ class VimState
       'find-backwards': (e) => new Motions.Find(@editor, this).reverse()
       'till': (e) => new Motions.Till(@editor, this)
       'till-backwards': (e) => new Motions.Till(@editor, this).reverse()
       'find-backwards': (e) => new Motions.Find(@editor, this).reverse()
       'till': (e) => new Motions.Till(@editor, this)
       'till-backwards': (e) => new Motions.Till(@editor, this).reverse()
-      'repeat-find': (e) => @currentFind.repeat() if @currentFind?
-      'repeat-find-reverse': (e) => @currentFind.repeat(reverse: true) if @currentFind?
+      'repeat-find': (e) => new @globalVimState.currentFind.constructor(@editor, this, repeated: true) if @globalVimState.currentFind
+      'repeat-find-reverse': (e) => new @globalVimState.currentFind.constructor(@editor, this, repeated: true, reverse: true) if @globalVimState.currentFind
       'replace': (e) => new Operators.Replace(@editor, this)
       'search': (e) => new Motions.Search(@editor, this)
       'reverse-search': (e) => (new Motions.Search(@editor, this)).reversed()
       'replace': (e) => new Operators.Replace(@editor, this)
       'search': (e) => new Motions.Search(@editor, this)
       'reverse-search': (e) => (new Motions.Search(@editor, this)).reversed()
@@ -210,7 +221,7 @@ class VimState
       # if we have started an operation that responds to canComposeWith check if it can compose
       # with the operation we're going to push onto the stack
       if (topOp = @topOperation())? and topOp.canComposeWith? and not topOp.canComposeWith(operation)
       # if we have started an operation that responds to canComposeWith check if it can compose
       # with the operation we're going to push onto the stack
       if (topOp = @topOperation())? and topOp.canComposeWith? and not topOp.canComposeWith(operation)
-        @resetCommandMode()
+        @resetNormalMode()
         @emitter.emit('failed-to-compose')
         break
 
         @emitter.emit('failed-to-compose')
         break
 
@@ -237,7 +248,7 @@ class VimState
 
   undo: ->
     @editor.undo()
 
   undo: ->
     @editor.undo()
-    @activateCommandMode()
+    @activateNormalMode()
 
   # Private: Processes the command if the last operation is complete.
   #
 
   # Private: Processes the command if the last operation is complete.
   #
@@ -247,7 +258,7 @@ class VimState
       return
 
     unless @topOperation().isComplete()
       return
 
     unless @topOperation().isComplete()
-      if @mode is 'command' and @topOperation() instanceof Operators.Operator
+      if @mode is 'normal' and @topOperation() instanceof Operators.Operator
         @activateOperatorPendingMode()
       return
 
         @activateOperatorPendingMode()
       return
 
@@ -258,7 +269,7 @@ class VimState
         @processOpStack()
       catch e
         if (e instanceof Operators.OperatorError) or (e instanceof Motions.MotionError)
         @processOpStack()
       catch e
         if (e instanceof Operators.OperatorError) or (e instanceof Motions.MotionError)
-          @resetCommandMode()
+          @resetNormalMode()
         else
           throw e
     else
         else
           throw e
     else
@@ -278,6 +289,8 @@ class VimState
   # Returns the value of the given register or undefined if it hasn't
   # been set.
   getRegister: (name) ->
   # Returns the value of the given register or undefined if it hasn't
   # been set.
   getRegister: (name) ->
+    if name is '"'
+      name = settings.defaultRegister()
     if name in ['*', '+']
       text = atom.clipboard.read()
       type = Utils.copyType(text)
     if name in ['*', '+']
       text = atom.clipboard.read()
       type = Utils.copyType(text)
@@ -312,6 +325,8 @@ class VimState
   #
   # Returns nothing.
   setRegister: (name, value) ->
   #
   # Returns nothing.
   setRegister: (name, value) ->
+    if name is '"'
+      name = settings.defaultRegister()
     if name in ['*', '+']
       atom.clipboard.write(value.text)
     else if name is '_'
     if name in ['*', '+']
       atom.clipboard.write(value.text)
     else if name is '_'
@@ -368,43 +383,73 @@ class VimState
   # Mode Switching
   ##############################################################################
 
   # Mode Switching
   ##############################################################################
 
-  # Private: Used to enable command mode.
+  # Private: Used to enable normal mode.
   #
   # Returns nothing.
   #
   # Returns nothing.
-  activateCommandMode: ->
+  activateNormalMode: ->
     @deactivateInsertMode()
     @deactivateVisualMode()
 
     @deactivateInsertMode()
     @deactivateVisualMode()
 
-    @mode = 'command'
+    @mode = 'normal'
     @submode = null
 
     @submode = null
 
-    @changeModeClass('command-mode')
+    @changeModeClass('normal-mode')
 
     @clearOpStack()
     selection.clear(autoscroll: false) for selection in @editor.getSelections()
 
     @clearOpStack()
     selection.clear(autoscroll: false) for selection in @editor.getSelections()
-    for cursor in @editor.getCursors()
-      if cursor.isAtEndOfLine() and not cursor.isAtBeginningOfLine()
-        cursor.moveLeft()
+    @ensureCursorsWithinLine()
 
     @updateStatusBar()
 
 
     @updateStatusBar()
 
+  # TODO: remove this method and bump the `vim-mode` service version number.
+  activateCommandMode: ->
+    Grim.deprecate("Use ::activateNormalMode instead")
+    @activateNormalMode()
+
   # Private: Used to enable insert mode.
   #
   # Returns nothing.
   # Private: Used to enable insert mode.
   #
   # Returns nothing.
-  activateInsertMode: ->
+  activateInsertMode: (subtype = null) ->
     @mode = 'insert'
     @editorElement.component.setInputEnabled(true)
     @setInsertionCheckpoint()
     @mode = 'insert'
     @editorElement.component.setInputEnabled(true)
     @setInsertionCheckpoint()
-    @submode = null
+    @submode = subtype
     @changeModeClass('insert-mode')
     @updateStatusBar()
 
     @changeModeClass('insert-mode')
     @updateStatusBar()
 
+  activateReplaceMode: ->
+    @activateInsertMode('replace')
+    @replaceModeCounter = 0
+    @editorElement.classList.add('replace-mode')
+    @subscriptions.add @replaceModeListener = @editor.onWillInsertText @replaceModeInsertHandler
+    @subscriptions.add @replaceModeUndoListener = @editor.onDidInsertText @replaceModeUndoHandler
+
+  replaceModeInsertHandler: (event) =>
+    chars = event.text?.split('') or []
+    selections = @editor.getSelections()
+    for char in chars
+      continue if char is '\n'
+      for selection in selections
+        selection.delete() unless selection.cursor.isAtEndOfLine()
+    return
+
+  replaceModeUndoHandler: (event) =>
+    @replaceModeCounter++
+
+  replaceModeUndo: ->
+    if @replaceModeCounter > 0
+      @editor.undo()
+      @editor.undo()
+      @editor.moveLeft()
+      @replaceModeCounter--
+
   setInsertionCheckpoint: ->
     @insertionCheckpoint = @editor.createCheckpoint() unless @insertionCheckpoint?
 
   deactivateInsertMode: ->
     return unless @mode in [null, 'insert']
     @editorElement.component.setInputEnabled(false)
   setInsertionCheckpoint: ->
     @insertionCheckpoint = @editor.createCheckpoint() unless @insertionCheckpoint?
 
   deactivateInsertMode: ->
     return unless @mode in [null, 'insert']
     @editorElement.component.setInputEnabled(false)
+    @editorElement.classList.remove('replace-mode')
     @editor.groupChangesSinceCheckpoint(@insertionCheckpoint)
     changes = getChangesSinceCheckpoint(@editor.buffer, @insertionCheckpoint)
     item = @inputOperator(@history[0])
     @editor.groupChangesSinceCheckpoint(@insertionCheckpoint)
     changes = getChangesSinceCheckpoint(@editor.buffer, @insertionCheckpoint)
     item = @inputOperator(@history[0])
@@ -413,6 +458,13 @@ class VimState
       item.confirmChanges(changes)
     for cursor in @editor.getCursors()
       cursor.moveLeft() unless cursor.isAtBeginningOfLine()
       item.confirmChanges(changes)
     for cursor in @editor.getCursors()
       cursor.moveLeft() unless cursor.isAtBeginningOfLine()
+    if @replaceModeListener?
+      @replaceModeListener.dispose()
+      @subscriptions.remove @replaceModeListener
+      @replaceModeListener = null
+      @replaceModeUndoListener.dispose()
+      @subscriptions.remove @replaceModeUndoListener
+      @replaceModeUndoListener = null
 
   deactivateVisualMode: ->
     return unless @mode is 'visual'
 
   deactivateVisualMode: ->
     return unless @mode is 'visual'
@@ -440,7 +492,7 @@ class VimState
     #  * activate-linewise-visual-mode
     if @mode is 'visual'
       if @submode is type
     #  * activate-linewise-visual-mode
     if @mode is 'visual'
       if @submode is type
-        @activateCommandMode()
+        @activateNormalMode()
         return
 
       @submode = type
         return
 
       @submode = type
@@ -486,25 +538,25 @@ class VimState
   activateOperatorPendingMode: ->
     @deactivateInsertMode()
     @mode = 'operator-pending'
   activateOperatorPendingMode: ->
     @deactivateInsertMode()
     @mode = 'operator-pending'
-    @submodule = null
+    @submode = null
     @changeModeClass('operator-pending-mode')
 
     @updateStatusBar()
 
   changeModeClass: (targetMode) ->
     @changeModeClass('operator-pending-mode')
 
     @updateStatusBar()
 
   changeModeClass: (targetMode) ->
-    for mode in ['command-mode', 'insert-mode', 'visual-mode', 'operator-pending-mode']
+    for mode in ['normal-mode', 'insert-mode', 'visual-mode', 'operator-pending-mode']
       if mode is targetMode
         @editorElement.classList.add(mode)
       else
         @editorElement.classList.remove(mode)
 
       if mode is targetMode
         @editorElement.classList.add(mode)
       else
         @editorElement.classList.remove(mode)
 
-  # Private: Resets the command mode back to it's initial state.
+  # Private: Resets the normal mode back to it's initial state.
   #
   # Returns nothing.
   #
   # Returns nothing.
-  resetCommandMode: ->
+  resetNormalMode: ->
     @clearOpStack()
     @editor.clearSelections()
     @clearOpStack()
     @editor.clearSelections()
-    @activateCommandMode()
+    @activateNormalMode()
 
   # Private: A generic way to create a Register prefix based on the event.
   #
 
   # Private: A generic way to create a Register prefix based on the event.
   #
@@ -512,11 +564,19 @@ class VimState
   #
   # Returns nothing.
   registerPrefix: (e) ->
   #
   # Returns nothing.
   registerPrefix: (e) ->
+    new Prefixes.Register(@registerName(e))
+
+  # Private: Gets a register name from a keyboard event
+  #
+  # e - The event
+  #
+  # Returns the name of the register
+  registerName: (e) ->
     keyboardEvent = e.originalEvent?.originalEvent ? e.originalEvent
     name = atom.keymaps.keystrokeForKeyboardEvent(keyboardEvent)
     if name.lastIndexOf('shift-', 0) is 0
       name = name.slice(6)
     keyboardEvent = e.originalEvent?.originalEvent ? e.originalEvent
     name = atom.keymaps.keystrokeForKeyboardEvent(keyboardEvent)
     if name.lastIndexOf('shift-', 0) is 0
       name = name.slice(6)
-    new Prefixes.Register(name)
+    name
 
   # Private: A generic way to create a Number prefix based on the event.
   #
 
   # Private: A generic way to create a Number prefix based on the event.
   #
@@ -535,8 +595,8 @@ class VimState
         @pushOperations(new Prefixes.Repeat(num))
 
   reverseSelections: ->
         @pushOperations(new Prefixes.Repeat(num))
 
   reverseSelections: ->
+    reversed = not @editor.getLastSelection().isReversed()
     for selection in @editor.getSelections()
     for selection in @editor.getSelections()
-      reversed = not selection.isReversed()
       selection.setBufferRange(selection.getBufferRange(), {reversed})
 
   # Private: Figure out whether or not we are in a repeat sequence or we just
       selection.setBufferRange(selection.getBufferRange(), {reversed})
 
   # Private: Figure out whether or not we are in a repeat sequence or we just
@@ -581,6 +641,34 @@ class VimState
   updateStatusBar: ->
     @statusBarManager.update(@mode, @submode)
 
   updateStatusBar: ->
     @statusBarManager.update(@mode, @submode)
 
+  # Private: insert the contents of the register in the editor
+  #
+  # name - the name of the register to insert
+  #
+  # Returns nothing.
+  insertRegister: (name) ->
+    text = @getRegister(name)?.text
+    @editor.insertText(text) if text?
+
+  # Private: ensure the mode follows the state of selections
+  checkSelections: =>
+    return unless @editor?
+    if @editor.getSelections().every((selection) -> selection.isEmpty())
+      @ensureCursorsWithinLine() if @mode is 'normal'
+      @activateNormalMode() if @mode is 'visual'
+    else
+      @activateVisualMode('characterwise') if @mode is 'normal'
+
+  # Private: ensure the cursor stays within the line as appropriate
+  ensureCursorsWithinLine: =>
+    for cursor in @editor.getCursors()
+      {goalColumn} = cursor
+      if cursor.isAtEndOfLine() and not cursor.isAtBeginningOfLine()
+        cursor.moveLeft()
+      cursor.goalColumn = goalColumn
+
+    @editor.mergeCursors()
+
 # This uses private APIs and may break if TextBuffer is refactored.
 # Package authors - copy and paste this code at your own risk.
 getChangesSinceCheckpoint = (buffer, checkpoint) ->
 # This uses private APIs and may break if TextBuffer is refactored.
 # Package authors - copy and paste this code at your own risk.
 getChangesSinceCheckpoint = (buffer, checkpoint) ->