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'
17 # selectOptions - The options object to pass through to the motion when
19 constructor: (@editor, @vimState, {@selectOptions}={}) ->
22 # Public: Determines when the command can be executed.
24 # Returns true if ready to execute and false otherwise.
25 isComplete: -> @complete
27 # Public: Determines if this command should be recorded in the command
28 # history for repeats.
30 # Returns true if this command should be recorded.
33 # Public: Marks this as ready to execute and saves the motion.
35 # motion - The motion used to select what to operate on.
40 throw new OperatorError('Must compose with a motion')
45 canComposeWith: (operation) -> operation.select?
47 # Public: Preps text and sets the text register
50 setTextRegister: (register, text) ->
51 if @motion?.isLinewise?()
53 if text[-1..] isnt '\n'
56 type = Utils.copyType(text)
57 @vimState.setRegister(register, {text, type}) unless text is ''
59 # Public: Generic class for an operator that requires extra input
60 class OperatorWithInput extends Operator
61 constructor: (@editor, @vimState) ->
65 canComposeWith: (operation) -> operation.characters? or operation.select?
67 compose: (operation) ->
70 if operation.characters?
75 # It deletes everything selected by the following motion.
77 class Delete extends Operator
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}={}) ->
86 @selectOptions.requireEOL ?= true
87 @register = settings.defaultRegister()
89 # Public: Deletes the text selected by the given motion.
91 # count - The number of times to execute.
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()
103 cursor.moveLeft() if cursor.isAtEndOfLine() and not cursor.isAtBeginningOfLine()
105 @vimState.activateCommandMode()
108 # It toggles the case of everything selected by the following motion
110 class ToggleCase extends Operator
111 constructor: (@editor, @vimState, {@complete, @selectOptions}={}) ->
113 execute: (count=1) ->
115 if _.contains(@motion.select(count, @selectOptions), true)
116 @editor.replaceSelectedText {}, (text) ->
117 text.split('').map((char) ->
118 lower = char.toLowerCase()
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)
131 _.times cursorCount, =>
132 point = cursor.getBufferPosition()
133 range = Range.fromPointWithDelta(point, 0, 1)
134 char = @editor.getTextInBufferRange(range)
136 if char is char.toLowerCase()
137 @editor.setTextInBufferRange(range, char.toUpperCase())
139 @editor.setTextInBufferRange(range, char.toLowerCase())
141 cursor.moveRight() unless point.column >= lineLength - 1
143 @vimState.activateCommandMode()
146 # In visual mode or after `g` with a motion, it makes the selection uppercase
148 class UpperCase extends Operator
149 constructor: (@editor, @vimState, {@selectOptions}={}) ->
152 execute: (count=1) ->
153 if _.contains(@motion.select(count, @selectOptions), true)
154 @editor.replaceSelectedText {}, (text) ->
157 @vimState.activateCommandMode()
160 # In visual mode or after `g` with a motion, it makes the selection lowercase
162 class LowerCase extends Operator
163 constructor: (@editor, @vimState, {@selectOptions}={}) ->
166 execute: (count=1) ->
167 if _.contains(@motion.select(count, @selectOptions), true)
168 @editor.replaceSelectedText {}, (text) ->
171 @vimState.activateCommandMode()
174 # It copies everything selected by the following motion.
176 class Yank extends Operator
179 constructor: (@editor, @vimState, {@allowEOL, @selectOptions}={}) ->
180 @register = settings.defaultRegister()
182 # Public: Copies the text selected by the given motion.
184 # count - The number of times to execute.
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])
199 newPositions = originalPositions
201 @setTextRegister(@register, text)
203 @editor.setSelectedBufferRanges(newPositions.map (p) -> new Range(p, p))
204 @vimState.activateCommandMode()
207 # It combines the current line with the following line.
209 class Join extends Operator
210 constructor: (@editor, @vimState, {@selectOptions}={}) -> @complete = true
212 # Public: Combines the current with the following lines
214 # count - The number of times to execute.
217 execute: (count=1) ->
221 @vimState.activateCommandMode()
224 # Repeat the last operation
226 class Repeat extends Operator
227 constructor: (@editor, @vimState, {@selectOptions}={}) -> @complete = true
229 isRecordable: -> false
231 execute: (count=1) ->
234 cmd = @vimState.history[0]
237 # It creates a mark at the current cursor position
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)
244 # Public: Creates the mark in the specified mark register (from user input)
245 # at the current position
249 @vimState.setMark(@input.characters, @editor.getCursorBufferPosition())
250 @vimState.activateCommandMode()
253 Operator, OperatorWithInput, OperatorError, Delete, ToggleCase,
254 UpperCase, LowerCase, Yank, Join, Repeat, Mark