1 _ = require 'underscore-plus'
2 {Point, Range} = require 'atom'
3 {ViewModel} = require '../view-models/view-model'
4 Utils = require '../utils'
5 settings = require '../settings'
8 constructor: (@message) ->
9 @name = 'Operator Error'
16 constructor: (@editor, @vimState) ->
19 # Public: Determines when the command can be executed.
21 # Returns true if ready to execute and false otherwise.
22 isComplete: -> @complete
24 # Public: Determines if this command should be recorded in the command
25 # history for repeats.
27 # Returns true if this command should be recorded.
30 # Public: Marks this as ready to execute and saves the motion.
32 # motion - The motion used to select what to operate on.
37 throw new OperatorError('Must compose with a motion')
42 canComposeWith: (operation) -> operation.select?
44 # Public: Preps text and sets the text register
47 setTextRegister: (register, text) ->
48 if @motion?.isLinewise?()
50 if text[-1..] isnt '\n'
53 type = Utils.copyType(text)
54 @vimState.setRegister(register, {text, type}) unless text is ''
56 # Public: Generic class for an operator that requires extra input
57 class OperatorWithInput extends Operator
58 constructor: (@editor, @vimState) ->
62 canComposeWith: (operation) -> operation.characters? or operation.select?
64 compose: (operation) ->
67 if operation.characters?
72 # It deletes everything selected by the following motion.
74 class Delete extends Operator
77 constructor: (@editor, @vimState) ->
79 @register = settings.defaultRegister()
81 # Public: Deletes the text selected by the given motion.
83 # count - The number of times to execute.
87 if _.contains(@motion.select(count), true)
88 @setTextRegister(@register, @editor.getSelectedText())
90 for selection in @editor.getSelections()
91 selection.deleteSelectedText()
92 for cursor in @editor.getCursors()
93 if @motion.isLinewise?()
94 cursor.skipLeadingWhitespace()
96 cursor.moveLeft() if cursor.isAtEndOfLine() and not cursor.isAtBeginningOfLine()
98 @vimState.activateNormalMode()
101 # It toggles the case of everything selected by the following motion
103 class ToggleCase extends Operator
104 constructor: (@editor, @vimState, {@complete}={}) ->
108 if _.contains(@motion.select(count), true)
109 @editor.replaceSelectedText {}, (text) ->
110 text.split('').map((char) ->
111 lower = char.toLowerCase()
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)
124 _.times cursorCount, =>
125 point = cursor.getBufferPosition()
126 range = Range.fromPointWithDelta(point, 0, 1)
127 char = @editor.getTextInBufferRange(range)
129 if char is char.toLowerCase()
130 @editor.setTextInBufferRange(range, char.toUpperCase())
132 @editor.setTextInBufferRange(range, char.toLowerCase())
134 cursor.moveRight() unless point.column >= lineLength - 1
136 @vimState.activateNormalMode()
139 # In visual mode or after `g` with a motion, it makes the selection uppercase
141 class UpperCase extends Operator
142 constructor: (@editor, @vimState) ->
146 if _.contains(@motion.select(count), true)
147 @editor.replaceSelectedText {}, (text) ->
150 @vimState.activateNormalMode()
153 # In visual mode or after `g` with a motion, it makes the selection lowercase
155 class LowerCase extends Operator
156 constructor: (@editor, @vimState) ->
160 if _.contains(@motion.select(count), true)
161 @editor.replaceSelectedText {}, (text) ->
164 @vimState.activateNormalMode()
167 # It copies everything selected by the following motion.
169 class Yank extends Operator
172 constructor: (@editor, @vimState) ->
173 @register = settings.defaultRegister()
175 # Public: Copies the text selected by the given motion.
177 # count - The number of times to execute.
181 oldTop = @editor.getScrollTop()
182 oldLeft = @editor.getScrollLeft()
183 oldLastCursorPosition = @editor.getCursorBufferPosition()
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
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)
199 newPositions = originalPositions
201 @setTextRegister(@register, text)
203 @editor.setSelectedBufferRanges(newPositions.map (p) -> new Range(p, p))
205 if oldLastCursorPosition.isEqual(@editor.getCursorBufferPosition())
206 @editor.setScrollLeft(oldLeft)
207 @editor.setScrollTop(oldTop)
209 @vimState.activateNormalMode()
212 # It combines the current line with the following line.
214 class Join extends Operator
215 constructor: (@editor, @vimState) -> @complete = true
217 # Public: Combines the current with the following lines
219 # count - The number of times to execute.
222 execute: (count=1) ->
226 @vimState.activateNormalMode()
229 # Repeat the last operation
231 class Repeat extends Operator
232 constructor: (@editor, @vimState) -> @complete = true
234 isRecordable: -> false
236 execute: (count=1) ->
239 cmd = @vimState.history[0]
242 # It creates a mark at the current cursor position
244 class Mark extends OperatorWithInput
245 constructor: (@editor, @vimState) ->
246 super(@editor, @vimState)
247 @viewModel = new ViewModel(this, class: 'mark', singleChar: true, hidden: true)
249 # Public: Creates the mark in the specified mark register (from user input)
250 # at the current position
254 @vimState.setMark(@input.characters, @editor.getCursorBufferPosition())
255 @vimState.activateNormalMode()
258 Operator, OperatorWithInput, OperatorError, Delete, ToggleCase,
259 UpperCase, LowerCase, Yank, Join, Repeat, Mark