]> git.r.bdr.sh - rbdr/dotfiles/blob - atom/packages/vim-mode/lib/operators/general-operators.coffee
Merge master
[rbdr/dotfiles] / atom / packages / vim-mode / lib / operators / general-operators.coffee
1 _ = require 'underscore-plus'
2 {Point, Range} = require 'atom'
3 {ViewModel} = require '../view-models/view-model'
4 Utils = require '../utils'
5 settings = require '../settings'
6
7 class OperatorError
8 constructor: (@message) ->
9 @name = 'Operator Error'
10
11 class Operator
12 vimState: null
13 motion: null
14 complete: null
15
16 constructor: (@editor, @vimState) ->
17 @complete = false
18
19 # Public: Determines when the command can be executed.
20 #
21 # Returns true if ready to execute and false otherwise.
22 isComplete: -> @complete
23
24 # Public: Determines if this command should be recorded in the command
25 # history for repeats.
26 #
27 # Returns true if this command should be recorded.
28 isRecordable: -> true
29
30 # Public: Marks this as ready to execute and saves the motion.
31 #
32 # motion - The motion used to select what to operate on.
33 #
34 # Returns nothing.
35 compose: (motion) ->
36 if not motion.select
37 throw new OperatorError('Must compose with a motion')
38
39 @motion = motion
40 @complete = true
41
42 canComposeWith: (operation) -> operation.select?
43
44 # Public: Preps text and sets the text register
45 #
46 # Returns nothing
47 setTextRegister: (register, text) ->
48 if @motion?.isLinewise?()
49 type = 'linewise'
50 if text[-1..] isnt '\n'
51 text += '\n'
52 else
53 type = Utils.copyType(text)
54 @vimState.setRegister(register, {text, type}) unless text is ''
55
56 # Public: Generic class for an operator that requires extra input
57 class OperatorWithInput extends Operator
58 constructor: (@editor, @vimState) ->
59 @editor = @editor
60 @complete = false
61
62 canComposeWith: (operation) -> operation.characters? or operation.select?
63
64 compose: (operation) ->
65 if operation.select?
66 @motion = operation
67 if operation.characters?
68 @input = operation
69 @complete = true
70
71 #
72 # It deletes everything selected by the following motion.
73 #
74 class Delete extends Operator
75 register: null
76
77 constructor: (@editor, @vimState) ->
78 @complete = false
79 @register = settings.defaultRegister()
80
81 # Public: Deletes the text selected by the given motion.
82 #
83 # count - The number of times to execute.
84 #
85 # Returns nothing.
86 execute: (count) ->
87 if _.contains(@motion.select(count), true)
88 @setTextRegister(@register, @editor.getSelectedText())
89 @editor.transact =>
90 for selection in @editor.getSelections()
91 selection.deleteSelectedText()
92 for cursor in @editor.getCursors()
93 if @motion.isLinewise?()
94 cursor.skipLeadingWhitespace()
95 else
96 cursor.moveLeft() if cursor.isAtEndOfLine() and not cursor.isAtBeginningOfLine()
97
98 @vimState.activateNormalMode()
99
100 #
101 # It toggles the case of everything selected by the following motion
102 #
103 class ToggleCase extends Operator
104 constructor: (@editor, @vimState, {@complete}={}) ->
105
106 execute: (count) ->
107 if @motion?
108 if _.contains(@motion.select(count), true)
109 @editor.replaceSelectedText {}, (text) ->
110 text.split('').map((char) ->
111 lower = char.toLowerCase()
112 if char is lower
113 char.toUpperCase()
114 else
115 lower
116 ).join('')
117 else
118 @editor.transact =>
119 for cursor in @editor.getCursors()
120 point = cursor.getBufferPosition()
121 lineLength = @editor.lineTextForBufferRow(point.row).length
122 cursorCount = Math.min(count ? 1, lineLength - point.column)
123
124 _.times cursorCount, =>
125 point = cursor.getBufferPosition()
126 range = Range.fromPointWithDelta(point, 0, 1)
127 char = @editor.getTextInBufferRange(range)
128
129 if char is char.toLowerCase()
130 @editor.setTextInBufferRange(range, char.toUpperCase())
131 else
132 @editor.setTextInBufferRange(range, char.toLowerCase())
133
134 cursor.moveRight() unless point.column >= lineLength - 1
135
136 @vimState.activateNormalMode()
137
138 #
139 # In visual mode or after `g` with a motion, it makes the selection uppercase
140 #
141 class UpperCase extends Operator
142 constructor: (@editor, @vimState) ->
143 @complete = false
144
145 execute: (count) ->
146 if _.contains(@motion.select(count), true)
147 @editor.replaceSelectedText {}, (text) ->
148 text.toUpperCase()
149
150 @vimState.activateNormalMode()
151
152 #
153 # In visual mode or after `g` with a motion, it makes the selection lowercase
154 #
155 class LowerCase extends Operator
156 constructor: (@editor, @vimState) ->
157 @complete = false
158
159 execute: (count) ->
160 if _.contains(@motion.select(count), true)
161 @editor.replaceSelectedText {}, (text) ->
162 text.toLowerCase()
163
164 @vimState.activateNormalMode()
165
166 #
167 # It copies everything selected by the following motion.
168 #
169 class Yank extends Operator
170 register: null
171
172 constructor: (@editor, @vimState) ->
173 @register = settings.defaultRegister()
174
175 # Public: Copies the text selected by the given motion.
176 #
177 # count - The number of times to execute.
178 #
179 # Returns nothing.
180 execute: (count) ->
181 oldTop = @editor.getScrollTop()
182 oldLeft = @editor.getScrollLeft()
183 oldLastCursorPosition = @editor.getCursorBufferPosition()
184
185 originalPositions = @editor.getCursorBufferPositions()
186 if _.contains(@motion.select(count), true)
187 text = @editor.getSelectedText()
188 startPositions = _.pluck(@editor.getSelectedBufferRanges(), "start")
189 newPositions = for originalPosition, i in originalPositions
190 if startPositions[i]
191 position = Point.min(startPositions[i], originalPositions[i])
192 if @vimState.mode isnt 'visual' and @motion.isLinewise?()
193 position = new Point(position.row, originalPositions[i].column)
194 position
195 else
196 originalPosition
197 else
198 text = ''
199 newPositions = originalPositions
200
201 @setTextRegister(@register, text)
202
203 @editor.setSelectedBufferRanges(newPositions.map (p) -> new Range(p, p))
204
205 if oldLastCursorPosition.isEqual(@editor.getCursorBufferPosition())
206 @editor.setScrollLeft(oldLeft)
207 @editor.setScrollTop(oldTop)
208
209 @vimState.activateNormalMode()
210
211 #
212 # It combines the current line with the following line.
213 #
214 class Join extends Operator
215 constructor: (@editor, @vimState) -> @complete = true
216
217 # Public: Combines the current with the following lines
218 #
219 # count - The number of times to execute.
220 #
221 # Returns nothing.
222 execute: (count=1) ->
223 @editor.transact =>
224 _.times count, =>
225 @editor.joinLines()
226 @vimState.activateNormalMode()
227
228 #
229 # Repeat the last operation
230 #
231 class Repeat extends Operator
232 constructor: (@editor, @vimState) -> @complete = true
233
234 isRecordable: -> false
235
236 execute: (count=1) ->
237 @editor.transact =>
238 _.times count, =>
239 cmd = @vimState.history[0]
240 cmd?.execute()
241 #
242 # It creates a mark at the current cursor position
243 #
244 class Mark extends OperatorWithInput
245 constructor: (@editor, @vimState) ->
246 super(@editor, @vimState)
247 @viewModel = new ViewModel(this, class: 'mark', singleChar: true, hidden: true)
248
249 # Public: Creates the mark in the specified mark register (from user input)
250 # at the current position
251 #
252 # Returns nothing.
253 execute: ->
254 @vimState.setMark(@input.characters, @editor.getCursorBufferPosition())
255 @vimState.activateNormalMode()
256
257 module.exports = {
258 Operator, OperatorWithInput, OperatorError, Delete, ToggleCase,
259 UpperCase, LowerCase, Yank, Join, Repeat, Mark
260 }