laughing-hipster/.atom/packages/vim-mode/lib/operators/general-operators.coffee

211 lines
5.7 KiB
CoffeeScript

_ = require 'underscore-plus'
{$$, Range} = require 'atom'
{ViewModel} = require '../view-models/view-model'
class OperatorError
constructor: (@message) ->
@name = 'Operator Error'
class Operator
vimState: null
motion: null
complete: null
selectOptions: null
# selectOptions - The options object to pass through to the motion when
# selecting.
constructor: (@editor, @vimState, {@selectOptions}={}) ->
@complete = false
# Public: Determines when the command can be executed.
#
# Returns true if ready to execute and false otherwise.
isComplete: -> @complete
# Public: Determines if this command should be recorded in the command
# history for repeats.
#
# Returns true if this command should be recorded.
isRecordable: -> true
# Public: Marks this as ready to execute and saves the motion.
#
# motion - The motion used to select what to operate on.
#
# Returns nothing.
compose: (motion) ->
if not motion.select
throw new OperatorError('Must compose with a motion')
@motion = motion
@complete = true
canComposeWith: (operation) -> operation.select?
# Protected: Wraps the function within an single undo step.
#
# fn - The function to wrap.
#
# Returns nothing.
undoTransaction: (fn) ->
@editor.getBuffer().transact(fn)
# Public: Generic class for an operator that requires extra input
class OperatorWithInput extends Operator
constructor: (@editorView, @vimState) ->
@editor = @editorView.editor
@complete = false
canComposeWith: (operation) -> operation.characters?
compose: (input) ->
if not input.characters
throw new OperatorError('Must compose with an Input')
@input = input
@complete = true
#
# It deletes everything selected by the following motion.
#
class Delete extends Operator
allowEOL: null
# allowEOL - Determines whether the cursor should be allowed to rest on the
# end of line character or not.
constructor: (@editor, @vimState, {@allowEOL, @selectOptions}={}) ->
@complete = false
@selectOptions ?= {}
@selectOptions.requireEOL ?= true
# Public: Deletes the text selected by the given motion.
#
# count - The number of times to execute.
#
# Returns nothing.
execute: (count=1) ->
cursor = @editor.getCursor()
if _.contains(@motion.select(count, @selectOptions), true)
validSelection = true
if validSelection?
@editor.delete()
if !@allowEOL and cursor.isAtEndOfLine() and !@motion.isLinewise?()
@editor.moveCursorLeft()
if @motion.isLinewise?()
@editor.setCursorScreenPosition([cursor.getScreenRow(), 0])
@vimState.activateCommandMode()
#
# It toggles the case of everything selected by the following motion
#
class ToggleCase extends Operator
constructor: (@editor, @vimState, {@selectOptions}={}) -> @complete = true
execute: (count=1) ->
pos = @editor.getCursorBufferPosition()
lastCharIndex = @editor.lineLengthForBufferRow(pos.row) - 1
count = Math.min count, @editor.lineLengthForBufferRow(pos.row) - pos.column
# Do nothing on an empty line
return if @editor.getBuffer().isRowBlank(pos.row)
@undoTransaction =>
_.times count, =>
point = @editor.getCursorBufferPosition()
range = Range.fromPointWithDelta(point, 0, 1)
char = @editor.getTextInBufferRange(range)
if char is char.toLowerCase()
@editor.setTextInBufferRange(range, char.toUpperCase())
else
@editor.setTextInBufferRange(range, char.toLowerCase())
unless point.column >= lastCharIndex
@editor.moveCursorRight()
@vimState.activateCommandMode()
#
# It copies everything selected by the following motion.
#
class Yank extends Operator
register: '"'
# Public: Copies the text selected by the given motion.
#
# count - The number of times to execute.
#
# Returns nothing.
execute: (count=1) ->
originalPosition = @editor.getCursorScreenPosition()
if _.contains(@motion.select(count), true)
text = @editor.getSelection().getText()
else
text = ''
type = if @motion.isLinewise?() then 'linewise' else 'character'
@vimState.setRegister(@register, {text, type})
if @motion.isLinewise?()
@editor.setCursorScreenPosition(originalPosition)
else
@editor.clearSelections()
@vimState.activateCommandMode()
#
# It combines the current line with the following line.
#
class Join extends Operator
constructor: (@editor, @vimState, {@selectOptions}={}) -> @complete = true
# Public: Combines the current with the following lines
#
# count - The number of times to execute.
#
# Returns nothing.
execute: (count=1) ->
@undoTransaction =>
_.times count, =>
@editor.joinLines()
@vimState.activateCommandMode()
#
# Repeat the last operation
#
class Repeat extends Operator
constructor: (@editor, @vimState, {@selectOptions}={}) -> @complete = true
isRecordable: -> false
execute: (count=1) ->
@undoTransaction =>
_.times count, =>
cmd = @vimState.history[0]
cmd?.execute()
#
# It creates a mark at the current cursor position
#
class Mark extends OperatorWithInput
constructor: (@editorView, @vimState, {@selectOptions}={}) ->
super(@editorView, @vimState)
@viewModel = new ViewModel(@, class: 'mark', singleChar: true, hidden: true)
# Public: Creates the mark in the specified mark register (from user input)
# at the current position
#
# Returns nothing.
execute: () ->
@vimState.setMark(@input.characters, @editorView.editor.getCursorBufferPosition())
@vimState.activateCommandMode()
module.exports = {
Operator, OperatorWithInput, OperatorError, Delete, ToggleCase,
Yank, Join, Repeat, Mark
}