]>
Commit | Line | Data |
---|---|---|
24c7594d BB |
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 | } |