aboutsummaryrefslogtreecommitdiff
path: root/atom/packages/vim-mode/lib/operators/input.coffee
diff options
context:
space:
mode:
authorBen Beltran <ben@nsovocal.com>2015-07-10 11:12:25 -0500
committerBen Beltran <ben@nsovocal.com>2015-07-10 11:12:25 -0500
commit24c7594d62d8d7fbbcdb64b11ce4adc5d8e6991a (patch)
treeded312222bb108923da1820ba40b04d710d20e5b /atom/packages/vim-mode/lib/operators/input.coffee
parenteb786e82d170e2abc351a432ade616d6ecdeeb6b (diff)
Adds atom
Diffstat (limited to 'atom/packages/vim-mode/lib/operators/input.coffee')
-rw-r--r--atom/packages/vim-mode/lib/operators/input.coffee231
1 files changed, 231 insertions, 0 deletions
diff --git a/atom/packages/vim-mode/lib/operators/input.coffee b/atom/packages/vim-mode/lib/operators/input.coffee
new file mode 100644
index 0000000..3821045
--- /dev/null
+++ b/atom/packages/vim-mode/lib/operators/input.coffee
@@ -0,0 +1,231 @@
+{Operator, Delete} = require './general-operators'
+_ = require 'underscore-plus'
+settings = require '../settings'
+
+# The operation for text entered in input mode. Broadly speaking, input
+# operators manage an undo transaction and set a @typingCompleted variable when
+# it's done. When the input operation is completed, the typingCompleted variable
+# tells the operation to repeat itself instead of enter insert mode.
+class Insert extends Operator
+ standalone: true
+
+ isComplete: -> @standalone or super
+
+ confirmChanges: (changes) ->
+ bundler = new TransactionBundler(changes, @editor)
+ @typedText = bundler.buildInsertText()
+
+ execute: ->
+ if @typingCompleted
+ return unless @typedText? and @typedText.length > 0
+ @editor.insertText(@typedText, normalizeLineEndings: true)
+ for cursor in @editor.getCursors()
+ cursor.moveLeft() unless cursor.isAtBeginningOfLine()
+ else
+ @vimState.activateInsertMode()
+ @typingCompleted = true
+ return
+
+ inputOperator: -> true
+
+class InsertAfter extends Insert
+ execute: ->
+ @editor.moveRight() unless @editor.getLastCursor().isAtEndOfLine()
+ super
+
+class InsertAfterEndOfLine extends Insert
+ execute: ->
+ @editor.moveToEndOfLine()
+ super
+
+class InsertAtBeginningOfLine extends Insert
+ execute: ->
+ @editor.moveToBeginningOfLine()
+ @editor.moveToFirstCharacterOfLine()
+ super
+
+class InsertAboveWithNewline extends Insert
+ execute: (count=1) ->
+ @vimState.setInsertionCheckpoint() unless @typingCompleted
+ @editor.insertNewlineAbove()
+ @editor.getLastCursor().skipLeadingWhitespace()
+
+ if @typingCompleted
+ # We'll have captured the inserted newline, but we want to do that
+ # over again by hand, or differing indentations will be wrong.
+ @typedText = @typedText.trimLeft()
+ return super
+
+ @vimState.activateInsertMode()
+ @typingCompleted = true
+
+class InsertBelowWithNewline extends Insert
+ execute: (count=1) ->
+ @vimState.setInsertionCheckpoint() unless @typingCompleted
+ @editor.insertNewlineBelow()
+ @editor.getLastCursor().skipLeadingWhitespace()
+
+ if @typingCompleted
+ # We'll have captured the inserted newline, but we want to do that
+ # over again by hand, or differing indentations will be wrong.
+ @typedText = @typedText.trimLeft()
+ return super
+
+ @vimState.activateInsertMode()
+ @typingCompleted = true
+
+#
+# Delete the following motion and enter insert mode to replace it.
+#
+class Change extends Insert
+ standalone: false
+ register: null
+
+ constructor: (@editor, @vimState, {@selectOptions}={}) ->
+ @register = settings.defaultRegister()
+
+ # Public: Changes the text selected by the given motion.
+ #
+ # count - The number of times to execute.
+ #
+ # Returns nothing.
+ execute: (count) ->
+ # If we've typed, we're being repeated. If we're being repeated,
+ # undo transactions are already handled.
+ @vimState.setInsertionCheckpoint() unless @typingCompleted
+
+ if _.contains(@motion.select(count, excludeWhitespace: true), true)
+ @setTextRegister(@register, @editor.getSelectedText())
+ if @motion.isLinewise?()
+ @editor.insertNewline()
+ @editor.moveLeft()
+ else
+ for selection in @editor.getSelections()
+ selection.deleteSelectedText()
+
+ return super if @typingCompleted
+
+ @vimState.activateInsertMode()
+ @typingCompleted = true
+
+class Substitute extends Insert
+ register: null
+
+ constructor: (@editor, @vimState, {@selectOptions}={}) ->
+ @register = settings.defaultRegister()
+
+ execute: (count=1) ->
+ @vimState.setInsertionCheckpoint() unless @typingCompleted
+ _.times count, =>
+ @editor.selectRight()
+ @setTextRegister(@register, @editor.getSelectedText())
+ @editor.delete()
+
+ if @typingCompleted
+ @typedText = @typedText.trimLeft()
+ return super
+
+ @vimState.activateInsertMode()
+ @typingCompleted = true
+
+class SubstituteLine extends Insert
+ register: null
+
+ constructor: (@editor, @vimState, {@selectOptions}={}) ->
+ @register = settings.defaultRegister()
+
+ execute: (count=1) ->
+ @vimState.setInsertionCheckpoint() unless @typingCompleted
+ @editor.moveToBeginningOfLine()
+ _.times count, =>
+ @editor.selectToEndOfLine()
+ @editor.selectRight()
+ @setTextRegister(@register, @editor.getSelectedText())
+ @editor.delete()
+ @editor.insertNewlineAbove()
+ @editor.getLastCursor().skipLeadingWhitespace()
+
+ if @typingCompleted
+ @typedText = @typedText.trimLeft()
+ return super
+
+ @vimState.activateInsertMode()
+ @typingCompleted = true
+
+# Takes a transaction and turns it into a string of what was typed.
+# This class is an implementation detail of Insert
+class TransactionBundler
+ constructor: (@changes, @editor) ->
+ @start = null
+ @end = null
+
+ buildInsertText: ->
+ @addChange(change) for change in @changes
+ if @start?
+ @editor.getTextInBufferRange [@start, @end]
+ else
+ ""
+
+ addChange: (change) ->
+ return unless change.newRange?
+ if @isRemovingFromPrevious(change)
+ @subtractRange change.oldRange
+ if @isAddingWithinPrevious(change)
+ @addRange change.newRange
+
+ isAddingWithinPrevious: (change) ->
+ return false unless @isAdding(change)
+
+ return true if @start is null
+
+ @start.isLessThanOrEqual(change.newRange.start) and
+ @end.isGreaterThanOrEqual(change.newRange.start)
+
+ isRemovingFromPrevious: (change) ->
+ return false unless @isRemoving(change) and @start?
+
+ @start.isLessThanOrEqual(change.oldRange.start) and
+ @end.isGreaterThanOrEqual(change.oldRange.end)
+
+ isAdding: (change) ->
+ change.newText.length > 0
+
+ isRemoving: (change) ->
+ change.oldText.length > 0
+
+ addRange: (range) ->
+ if @start is null
+ {@start, @end} = range
+ return
+
+ rows = range.end.row - range.start.row
+
+ if (range.start.row is @end.row)
+ cols = range.end.column - range.start.column
+ else
+ cols = 0
+
+ @end = @end.translate [rows, cols]
+
+ subtractRange: (range) ->
+ rows = range.end.row - range.start.row
+
+ if (range.end.row is @end.row)
+ cols = range.end.column - range.start.column
+ else
+ cols = 0
+
+ @end = @end.translate [-rows, -cols]
+
+
+module.exports = {
+ Insert,
+ InsertAfter,
+ InsertAfterEndOfLine,
+ InsertAtBeginningOfLine,
+ InsertAboveWithNewline,
+ InsertBelowWithNewline,
+ Change,
+ Substitute,
+ SubstituteLine
+}