1 {Operator, Delete} = require './general-operators'
2 _ = require 'underscore-plus'
3 settings = require '../settings'
5 # The operation for text entered in input mode. Broadly speaking, input
6 # operators manage an undo transaction and set a @typingCompleted variable when
7 # it's done. When the input operation is completed, the typingCompleted variable
8 # tells the operation to repeat itself instead of enter insert mode.
9 class Insert extends Operator
12 isComplete: -> @standalone or super
14 confirmChanges: (changes) ->
15 bundler = new TransactionBundler(changes, @editor)
16 @typedText = bundler.buildInsertText()
20 return unless @typedText? and @typedText.length > 0
21 @editor.insertText(@typedText, normalizeLineEndings: true)
22 for cursor in @editor.getCursors()
23 cursor.moveLeft() unless cursor.isAtBeginningOfLine()
25 @vimState.activateInsertMode()
26 @typingCompleted = true
29 inputOperator: -> true
31 class InsertAfter extends Insert
33 @editor.moveRight() unless @editor.getLastCursor().isAtEndOfLine()
36 class InsertAfterEndOfLine extends Insert
38 @editor.moveToEndOfLine()
41 class InsertAtBeginningOfLine extends Insert
43 @editor.moveToBeginningOfLine()
44 @editor.moveToFirstCharacterOfLine()
47 class InsertAboveWithNewline extends Insert
49 @vimState.setInsertionCheckpoint() unless @typingCompleted
50 @editor.insertNewlineAbove()
51 @editor.getLastCursor().skipLeadingWhitespace()
54 # We'll have captured the inserted newline, but we want to do that
55 # over again by hand, or differing indentations will be wrong.
56 @typedText = @typedText.trimLeft()
59 @vimState.activateInsertMode()
60 @typingCompleted = true
62 class InsertBelowWithNewline extends Insert
64 @vimState.setInsertionCheckpoint() unless @typingCompleted
65 @editor.insertNewlineBelow()
66 @editor.getLastCursor().skipLeadingWhitespace()
69 # We'll have captured the inserted newline, but we want to do that
70 # over again by hand, or differing indentations will be wrong.
71 @typedText = @typedText.trimLeft()
74 @vimState.activateInsertMode()
75 @typingCompleted = true
78 # Delete the following motion and enter insert mode to replace it.
80 class Change extends Insert
84 constructor: (@editor, @vimState, {@selectOptions}={}) ->
85 @register = settings.defaultRegister()
87 # Public: Changes the text selected by the given motion.
89 # count - The number of times to execute.
93 # If we've typed, we're being repeated. If we're being repeated,
94 # undo transactions are already handled.
95 @vimState.setInsertionCheckpoint() unless @typingCompleted
97 if _.contains(@motion.select(count, excludeWhitespace: true), true)
98 @setTextRegister(@register, @editor.getSelectedText())
99 if @motion.isLinewise?()
100 @editor.insertNewline()
103 for selection in @editor.getSelections()
104 selection.deleteSelectedText()
106 return super if @typingCompleted
108 @vimState.activateInsertMode()
109 @typingCompleted = true
111 class Substitute extends Insert
114 constructor: (@editor, @vimState, {@selectOptions}={}) ->
115 @register = settings.defaultRegister()
117 execute: (count=1) ->
118 @vimState.setInsertionCheckpoint() unless @typingCompleted
120 @editor.selectRight()
121 @setTextRegister(@register, @editor.getSelectedText())
125 @typedText = @typedText.trimLeft()
128 @vimState.activateInsertMode()
129 @typingCompleted = true
131 class SubstituteLine extends Insert
134 constructor: (@editor, @vimState, {@selectOptions}={}) ->
135 @register = settings.defaultRegister()
137 execute: (count=1) ->
138 @vimState.setInsertionCheckpoint() unless @typingCompleted
139 @editor.moveToBeginningOfLine()
141 @editor.selectToEndOfLine()
142 @editor.selectRight()
143 @setTextRegister(@register, @editor.getSelectedText())
145 @editor.insertNewlineAbove()
146 @editor.getLastCursor().skipLeadingWhitespace()
149 @typedText = @typedText.trimLeft()
152 @vimState.activateInsertMode()
153 @typingCompleted = true
155 # Takes a transaction and turns it into a string of what was typed.
156 # This class is an implementation detail of Insert
157 class TransactionBundler
158 constructor: (@changes, @editor) ->
163 @addChange(change) for change in @changes
165 @editor.getTextInBufferRange [@start, @end]
169 addChange: (change) ->
170 return unless change.newRange?
171 if @isRemovingFromPrevious(change)
172 @subtractRange change.oldRange
173 if @isAddingWithinPrevious(change)
174 @addRange change.newRange
176 isAddingWithinPrevious: (change) ->
177 return false unless @isAdding(change)
179 return true if @start is null
181 @start.isLessThanOrEqual(change.newRange.start) and
182 @end.isGreaterThanOrEqual(change.newRange.start)
184 isRemovingFromPrevious: (change) ->
185 return false unless @isRemoving(change) and @start?
187 @start.isLessThanOrEqual(change.oldRange.start) and
188 @end.isGreaterThanOrEqual(change.oldRange.end)
190 isAdding: (change) ->
191 change.newText.length > 0
193 isRemoving: (change) ->
194 change.oldText.length > 0
198 {@start, @end} = range
201 rows = range.end.row - range.start.row
203 if (range.start.row is @end.row)
204 cols = range.end.column - range.start.column
208 @end = @end.translate [rows, cols]
210 subtractRange: (range) ->
211 rows = range.end.row - range.start.row
213 if (range.end.row is @end.row)
214 cols = range.end.column - range.start.column
218 @end = @end.translate [-rows, -cols]
224 InsertAfterEndOfLine,
225 InsertAtBeginningOfLine,
226 InsertAboveWithNewline,
227 InsertBelowWithNewline,