1 Motions = require '../motions/index'
2 {Operator, Delete} = require './general-operators'
3 _ = require 'underscore-plus'
4 settings = require '../settings'
6 # The operation for text entered in input mode. Broadly speaking, input
7 # operators manage an undo transaction and set a @typingCompleted variable when
8 # it's done. When the input operation is completed, the typingCompleted variable
9 # tells the operation to repeat itself instead of enter insert mode.
10 class Insert extends Operator
13 isComplete: -> @standalone or super
15 confirmChanges: (changes) ->
16 bundler = new TransactionBundler(changes, @editor)
17 @typedText = bundler.buildInsertText()
21 return unless @typedText? and @typedText.length > 0
22 @editor.insertText(@typedText, normalizeLineEndings: true, autoIndent: true)
23 for cursor in @editor.getCursors()
24 cursor.moveLeft() unless cursor.isAtBeginningOfLine()
26 @vimState.activateInsertMode()
27 @typingCompleted = true
30 inputOperator: -> true
32 class ReplaceMode extends Insert
36 return unless @typedText? and @typedText.length > 0
38 @editor.insertText(@typedText, normalizeLineEndings: true)
39 toDelete = @typedText.length - @countChars('\n', @typedText)
40 for selection in @editor.getSelections()
42 selection.delete() while count-- and not selection.cursor.isAtEndOfLine()
43 for cursor in @editor.getCursors()
44 cursor.moveLeft() unless cursor.isAtBeginningOfLine()
46 @vimState.activateReplaceMode()
47 @typingCompleted = true
49 countChars: (char, string) ->
50 string.split(char).length - 1
52 class InsertAfter extends Insert
54 @editor.moveRight() unless @editor.getLastCursor().isAtEndOfLine()
57 class InsertAfterEndOfLine extends Insert
59 @editor.moveToEndOfLine()
62 class InsertAtBeginningOfLine extends Insert
64 @editor.moveToBeginningOfLine()
65 @editor.moveToFirstCharacterOfLine()
68 class InsertAboveWithNewline extends Insert
70 @vimState.setInsertionCheckpoint() unless @typingCompleted
71 @editor.insertNewlineAbove()
72 @editor.getLastCursor().skipLeadingWhitespace()
75 # We'll have captured the inserted newline, but we want to do that
76 # over again by hand, or differing indentations will be wrong.
77 @typedText = @typedText.trimLeft()
80 @vimState.activateInsertMode()
81 @typingCompleted = true
83 class InsertBelowWithNewline extends Insert
85 @vimState.setInsertionCheckpoint() unless @typingCompleted
86 @editor.insertNewlineBelow()
87 @editor.getLastCursor().skipLeadingWhitespace()
90 # We'll have captured the inserted newline, but we want to do that
91 # over again by hand, or differing indentations will be wrong.
92 @typedText = @typedText.trimLeft()
95 @vimState.activateInsertMode()
96 @typingCompleted = true
99 # Delete the following motion and enter insert mode to replace it.
101 class Change extends Insert
105 constructor: (@editor, @vimState) ->
106 @register = settings.defaultRegister()
108 # Public: Changes the text selected by the given motion.
110 # count - The number of times to execute.
114 if _.contains(@motion.select(count, excludeWhitespace: true), true)
115 # If we've typed, we're being repeated. If we're being repeated,
116 # undo transactions are already handled.
117 @vimState.setInsertionCheckpoint() unless @typingCompleted
119 @setTextRegister(@register, @editor.getSelectedText())
120 if @motion.isLinewise?() and not @typingCompleted
121 for selection in @editor.getSelections()
122 if selection.getBufferRange().end.row is 0
123 selection.deleteSelectedText()
125 selection.insertText("\n", autoIndent: true)
126 selection.cursor.moveLeft()
128 for selection in @editor.getSelections()
129 selection.deleteSelectedText()
131 return super if @typingCompleted
133 @vimState.activateInsertMode()
134 @typingCompleted = true
136 @vimState.activateNormalMode()
138 # Takes a transaction and turns it into a string of what was typed.
139 # This class is an implementation detail of Insert
140 class TransactionBundler
141 constructor: (@changes, @editor) ->
146 @addChange(change) for change in @changes
148 @editor.getTextInBufferRange [@start, @end]
152 addChange: (change) ->
153 return unless change.newRange?
154 if @isRemovingFromPrevious(change)
155 @subtractRange change.oldRange
156 if @isAddingWithinPrevious(change)
157 @addRange change.newRange
159 isAddingWithinPrevious: (change) ->
160 return false unless @isAdding(change)
162 return true if @start is null
164 @start.isLessThanOrEqual(change.newRange.start) and
165 @end.isGreaterThanOrEqual(change.newRange.start)
167 isRemovingFromPrevious: (change) ->
168 return false unless @isRemoving(change) and @start?
170 @start.isLessThanOrEqual(change.oldRange.start) and
171 @end.isGreaterThanOrEqual(change.oldRange.end)
173 isAdding: (change) ->
174 change.newText.length > 0
176 isRemoving: (change) ->
177 change.oldText.length > 0
181 {@start, @end} = range
184 rows = range.end.row - range.start.row
186 if (range.start.row is @end.row)
187 cols = range.end.column - range.start.column
191 @end = @end.translate [rows, cols]
193 subtractRange: (range) ->
194 rows = range.end.row - range.start.row
196 if (range.end.row is @end.row)
197 cols = range.end.column - range.start.column
201 @end = @end.translate [-rows, -cols]
207 InsertAfterEndOfLine,
208 InsertAtBeginningOfLine,
209 InsertAboveWithNewline,
210 InsertBelowWithNewline,