]> git.r.bdr.sh - rbdr/dotfiles/blob - atom/packages/vim-mode/lib/operators/input.coffee
3821045c0adbae7cb5cea9880e5bdea719d1939f
[rbdr/dotfiles] / atom / packages / vim-mode / lib / operators / input.coffee
1 {Operator, Delete} = require './general-operators'
2 _ = require 'underscore-plus'
3 settings = require '../settings'
4
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
10 standalone: true
11
12 isComplete: -> @standalone or super
13
14 confirmChanges: (changes) ->
15 bundler = new TransactionBundler(changes, @editor)
16 @typedText = bundler.buildInsertText()
17
18 execute: ->
19 if @typingCompleted
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()
24 else
25 @vimState.activateInsertMode()
26 @typingCompleted = true
27 return
28
29 inputOperator: -> true
30
31 class InsertAfter extends Insert
32 execute: ->
33 @editor.moveRight() unless @editor.getLastCursor().isAtEndOfLine()
34 super
35
36 class InsertAfterEndOfLine extends Insert
37 execute: ->
38 @editor.moveToEndOfLine()
39 super
40
41 class InsertAtBeginningOfLine extends Insert
42 execute: ->
43 @editor.moveToBeginningOfLine()
44 @editor.moveToFirstCharacterOfLine()
45 super
46
47 class InsertAboveWithNewline extends Insert
48 execute: (count=1) ->
49 @vimState.setInsertionCheckpoint() unless @typingCompleted
50 @editor.insertNewlineAbove()
51 @editor.getLastCursor().skipLeadingWhitespace()
52
53 if @typingCompleted
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()
57 return super
58
59 @vimState.activateInsertMode()
60 @typingCompleted = true
61
62 class InsertBelowWithNewline extends Insert
63 execute: (count=1) ->
64 @vimState.setInsertionCheckpoint() unless @typingCompleted
65 @editor.insertNewlineBelow()
66 @editor.getLastCursor().skipLeadingWhitespace()
67
68 if @typingCompleted
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()
72 return super
73
74 @vimState.activateInsertMode()
75 @typingCompleted = true
76
77 #
78 # Delete the following motion and enter insert mode to replace it.
79 #
80 class Change extends Insert
81 standalone: false
82 register: null
83
84 constructor: (@editor, @vimState, {@selectOptions}={}) ->
85 @register = settings.defaultRegister()
86
87 # Public: Changes the text selected by the given motion.
88 #
89 # count - The number of times to execute.
90 #
91 # Returns nothing.
92 execute: (count) ->
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
96
97 if _.contains(@motion.select(count, excludeWhitespace: true), true)
98 @setTextRegister(@register, @editor.getSelectedText())
99 if @motion.isLinewise?()
100 @editor.insertNewline()
101 @editor.moveLeft()
102 else
103 for selection in @editor.getSelections()
104 selection.deleteSelectedText()
105
106 return super if @typingCompleted
107
108 @vimState.activateInsertMode()
109 @typingCompleted = true
110
111 class Substitute extends Insert
112 register: null
113
114 constructor: (@editor, @vimState, {@selectOptions}={}) ->
115 @register = settings.defaultRegister()
116
117 execute: (count=1) ->
118 @vimState.setInsertionCheckpoint() unless @typingCompleted
119 _.times count, =>
120 @editor.selectRight()
121 @setTextRegister(@register, @editor.getSelectedText())
122 @editor.delete()
123
124 if @typingCompleted
125 @typedText = @typedText.trimLeft()
126 return super
127
128 @vimState.activateInsertMode()
129 @typingCompleted = true
130
131 class SubstituteLine extends Insert
132 register: null
133
134 constructor: (@editor, @vimState, {@selectOptions}={}) ->
135 @register = settings.defaultRegister()
136
137 execute: (count=1) ->
138 @vimState.setInsertionCheckpoint() unless @typingCompleted
139 @editor.moveToBeginningOfLine()
140 _.times count, =>
141 @editor.selectToEndOfLine()
142 @editor.selectRight()
143 @setTextRegister(@register, @editor.getSelectedText())
144 @editor.delete()
145 @editor.insertNewlineAbove()
146 @editor.getLastCursor().skipLeadingWhitespace()
147
148 if @typingCompleted
149 @typedText = @typedText.trimLeft()
150 return super
151
152 @vimState.activateInsertMode()
153 @typingCompleted = true
154
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) ->
159 @start = null
160 @end = null
161
162 buildInsertText: ->
163 @addChange(change) for change in @changes
164 if @start?
165 @editor.getTextInBufferRange [@start, @end]
166 else
167 ""
168
169 addChange: (change) ->
170 return unless change.newRange?
171 if @isRemovingFromPrevious(change)
172 @subtractRange change.oldRange
173 if @isAddingWithinPrevious(change)
174 @addRange change.newRange
175
176 isAddingWithinPrevious: (change) ->
177 return false unless @isAdding(change)
178
179 return true if @start is null
180
181 @start.isLessThanOrEqual(change.newRange.start) and
182 @end.isGreaterThanOrEqual(change.newRange.start)
183
184 isRemovingFromPrevious: (change) ->
185 return false unless @isRemoving(change) and @start?
186
187 @start.isLessThanOrEqual(change.oldRange.start) and
188 @end.isGreaterThanOrEqual(change.oldRange.end)
189
190 isAdding: (change) ->
191 change.newText.length > 0
192
193 isRemoving: (change) ->
194 change.oldText.length > 0
195
196 addRange: (range) ->
197 if @start is null
198 {@start, @end} = range
199 return
200
201 rows = range.end.row - range.start.row
202
203 if (range.start.row is @end.row)
204 cols = range.end.column - range.start.column
205 else
206 cols = 0
207
208 @end = @end.translate [rows, cols]
209
210 subtractRange: (range) ->
211 rows = range.end.row - range.start.row
212
213 if (range.end.row is @end.row)
214 cols = range.end.column - range.start.column
215 else
216 cols = 0
217
218 @end = @end.translate [-rows, -cols]
219
220
221 module.exports = {
222 Insert,
223 InsertAfter,
224 InsertAfterEndOfLine,
225 InsertAtBeginningOfLine,
226 InsertAboveWithNewline,
227 InsertBelowWithNewline,
228 Change,
229 Substitute,
230 SubstituteLine
231 }