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