]> git.r.bdr.sh - rbdr/dotfiles/blame - atom/packages/vim-mode/lib/vim-state.coffee
Adds atom
[rbdr/dotfiles] / atom / packages / vim-mode / lib / vim-state.coffee
CommitLineData
24c7594d
BB
1_ = require 'underscore-plus'
2{Point, Range} = require 'atom'
3{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
4settings = require './settings'
5
6Operators = require './operators/index'
7Prefixes = require './prefixes'
8Motions = require './motions/index'
9
10TextObjects = require './text-objects'
11Utils = require './utils'
12Scroll = require './scroll'
13
14module.exports =
15class VimState
16 editor: null
17 opStack: null
18 mode: null
19 submode: null
20 destroyed: false
21
22 constructor: (@editorElement, @statusBarManager, @globalVimState) ->
23 @emitter = new Emitter
24 @subscriptions = new CompositeDisposable
25 @editor = @editorElement.getModel()
26 @opStack = []
27 @history = []
28 @marks = {}
29 @subscriptions.add @editor.onDidDestroy => @destroy()
30
31 @subscriptions.add @editor.onDidChangeSelectionRange _.debounce(=>
32 return unless @editor?
33 if @editor.getSelections().every((selection) -> selection.isEmpty())
34 @activateCommandMode() if @mode is 'visual'
35 else
36 @activateVisualMode('characterwise') if @mode is 'command'
37 , 100)
38
39 @editorElement.classList.add("vim-mode")
40 @setupCommandMode()
41 if settings.startInInsertMode()
42 @activateInsertMode()
43 else
44 @activateCommandMode()
45
46 destroy: ->
47 unless @destroyed
48 @destroyed = true
49 @emitter.emit 'did-destroy'
50 @subscriptions.dispose()
51 if @editor.isAlive()
52 @deactivateInsertMode()
53 @editorElement.component?.setInputEnabled(true)
54 @editorElement.classList.remove("vim-mode")
55 @editorElement.classList.remove("command-mode")
56 @editor = null
57 @editorElement = null
58
59 # Private: Creates the plugin's bindings
60 #
61 # Returns nothing.
62 setupCommandMode: ->
63 @registerCommands
64 'activate-command-mode': => @activateCommandMode()
65 'activate-linewise-visual-mode': => @activateVisualMode('linewise')
66 'activate-characterwise-visual-mode': => @activateVisualMode('characterwise')
67 'activate-blockwise-visual-mode': => @activateVisualMode('blockwise')
68 'reset-command-mode': => @resetCommandMode()
69 'repeat-prefix': (e) => @repeatPrefix(e)
70 'reverse-selections': (e) => @reverseSelections(e)
71 'undo': (e) => @undo(e)
72
73 @registerOperationCommands
74 'activate-insert-mode': => new Operators.Insert(@editor, this)
75 'substitute': => new Operators.Substitute(@editor, this)
76 'substitute-line': => new Operators.SubstituteLine(@editor, this)
77 'insert-after': => new Operators.InsertAfter(@editor, this)
78 'insert-after-end-of-line': => new Operators.InsertAfterEndOfLine(@editor, this)
79 'insert-at-beginning-of-line': => new Operators.InsertAtBeginningOfLine(@editor, this)
80 'insert-above-with-newline': => new Operators.InsertAboveWithNewline(@editor, this)
81 'insert-below-with-newline': => new Operators.InsertBelowWithNewline(@editor, this)
82 'delete': => @linewiseAliasedOperator(Operators.Delete)
83 'change': => @linewiseAliasedOperator(Operators.Change)
84 'change-to-last-character-of-line': => [new Operators.Change(@editor, this), new Motions.MoveToLastCharacterOfLine(@editor, this)]
85 'delete-right': => [new Operators.Delete(@editor, this), new Motions.MoveRight(@editor, this)]
86 'delete-left': => [new Operators.Delete(@editor, this), new Motions.MoveLeft(@editor, this)]
87 'delete-to-last-character-of-line': => [new Operators.Delete(@editor, this), new Motions.MoveToLastCharacterOfLine(@editor, this)]
88 'toggle-case': => new Operators.ToggleCase(@editor, this)
89 'upper-case': => new Operators.UpperCase(@editor, this)
90 'lower-case': => new Operators.LowerCase(@editor, this)
91 'toggle-case-now': => new Operators.ToggleCase(@editor, this, complete: true)
92 'yank': => @linewiseAliasedOperator(Operators.Yank)
93 'yank-line': => [new Operators.Yank(@editor, this), new Motions.MoveToRelativeLine(@editor, this)]
94 'put-before': => new Operators.Put(@editor, this, location: 'before')
95 'put-after': => new Operators.Put(@editor, this, location: 'after')
96 'join': => new Operators.Join(@editor, this)
97 'indent': => @linewiseAliasedOperator(Operators.Indent)
98 'outdent': => @linewiseAliasedOperator(Operators.Outdent)
99 'auto-indent': => @linewiseAliasedOperator(Operators.Autoindent)
100 'increase': => new Operators.Increase(@editor, this)
101 'decrease': => new Operators.Decrease(@editor, this)
102 'move-left': => new Motions.MoveLeft(@editor, this)
103 'move-up': => new Motions.MoveUp(@editor, this)
104 'move-down': => new Motions.MoveDown(@editor, this)
105 'move-right': => new Motions.MoveRight(@editor, this)
106 'move-to-next-word': => new Motions.MoveToNextWord(@editor, this)
107 'move-to-next-whole-word': => new Motions.MoveToNextWholeWord(@editor, this)
108 'move-to-end-of-word': => new Motions.MoveToEndOfWord(@editor, this)
109 'move-to-end-of-whole-word': => new Motions.MoveToEndOfWholeWord(@editor, this)
110 'move-to-previous-word': => new Motions.MoveToPreviousWord(@editor, this)
111 'move-to-previous-whole-word': => new Motions.MoveToPreviousWholeWord(@editor, this)
112 'move-to-next-paragraph': => new Motions.MoveToNextParagraph(@editor, this)
113 'move-to-previous-paragraph': => new Motions.MoveToPreviousParagraph(@editor, this)
114 'move-to-first-character-of-line': => new Motions.MoveToFirstCharacterOfLine(@editor, this)
115 'move-to-first-character-of-line-and-down': => new Motions.MoveToFirstCharacterOfLineAndDown(@editor, this)
116 'move-to-last-character-of-line': => new Motions.MoveToLastCharacterOfLine(@editor, this)
117 'move-to-last-nonblank-character-of-line-and-down': => new Motions.MoveToLastNonblankCharacterOfLineAndDown(@editor, this)
118 'move-to-beginning-of-line': (e) => @moveOrRepeat(e)
119 'move-to-first-character-of-line-up': => new Motions.MoveToFirstCharacterOfLineUp(@editor, this)
120 'move-to-first-character-of-line-down': => new Motions.MoveToFirstCharacterOfLineDown(@editor, this)
121 'move-to-start-of-file': => new Motions.MoveToStartOfFile(@editor, this)
122 'move-to-line': => new Motions.MoveToAbsoluteLine(@editor, this)
123 'move-to-top-of-screen': => new Motions.MoveToTopOfScreen(@editorElement, this)
124 'move-to-bottom-of-screen': => new Motions.MoveToBottomOfScreen(@editorElement, this)
125 'move-to-middle-of-screen': => new Motions.MoveToMiddleOfScreen(@editorElement, this)
126 'scroll-down': => new Scroll.ScrollDown(@editorElement)
127 'scroll-up': => new Scroll.ScrollUp(@editorElement)
128 'scroll-cursor-to-top': => new Scroll.ScrollCursorToTop(@editorElement)
129 'scroll-cursor-to-top-leave': => new Scroll.ScrollCursorToTop(@editorElement, {leaveCursor: true})
130 'scroll-cursor-to-middle': => new Scroll.ScrollCursorToMiddle(@editorElement)
131 'scroll-cursor-to-middle-leave': => new Scroll.ScrollCursorToMiddle(@editorElement, {leaveCursor: true})
132 'scroll-cursor-to-bottom': => new Scroll.ScrollCursorToBottom(@editorElement)
133 'scroll-cursor-to-bottom-leave': => new Scroll.ScrollCursorToBottom(@editorElement, {leaveCursor: true})
134 'scroll-half-screen-up': => new Motions.ScrollHalfUpKeepCursor(@editorElement, this)
135 'scroll-full-screen-up': => new Motions.ScrollFullUpKeepCursor(@editorElement, this)
136 'scroll-half-screen-down': => new Motions.ScrollHalfDownKeepCursor(@editorElement, this)
137 'scroll-full-screen-down': => new Motions.ScrollFullDownKeepCursor(@editorElement, this)
138 'select-inside-word': => new TextObjects.SelectInsideWord(@editor)
139 'select-inside-double-quotes': => new TextObjects.SelectInsideQuotes(@editor, '"', false)
140 'select-inside-single-quotes': => new TextObjects.SelectInsideQuotes(@editor, '\'', false)
141 'select-inside-back-ticks': => new TextObjects.SelectInsideQuotes(@editor, '`', false)
142 'select-inside-curly-brackets': => new TextObjects.SelectInsideBrackets(@editor, '{', '}', false)
143 'select-inside-angle-brackets': => new TextObjects.SelectInsideBrackets(@editor, '<', '>', false)
144 'select-inside-tags': => new TextObjects.SelectInsideBrackets(@editor, '>', '<', false)
145 'select-inside-square-brackets': => new TextObjects.SelectInsideBrackets(@editor, '[', ']', false)
146 'select-inside-parentheses': => new TextObjects.SelectInsideBrackets(@editor, '(', ')', false)
147 'select-inside-paragraph': => new TextObjects.SelectInsideParagraph(@editor, false)
148 'select-a-word': => new TextObjects.SelectAWord(@editor)
149 'select-around-double-quotes': => new TextObjects.SelectInsideQuotes(@editor, '"', true)
150 'select-around-single-quotes': => new TextObjects.SelectInsideQuotes(@editor, '\'', true)
151 'select-around-back-ticks': => new TextObjects.SelectInsideQuotes(@editor, '`', true)
152 'select-around-curly-brackets': => new TextObjects.SelectInsideBrackets(@editor, '{', '}', true)
153 'select-around-angle-brackets': => new TextObjects.SelectInsideBrackets(@editor, '<', '>', true)
154 'select-around-square-brackets': => new TextObjects.SelectInsideBrackets(@editor, '[', ']', true)
155 'select-around-parentheses': => new TextObjects.SelectInsideBrackets(@editor, '(', ')', true)
156 'select-around-paragraph': => new TextObjects.SelectAParagraph(@editor, true)
157 'register-prefix': (e) => @registerPrefix(e)
158 'repeat': (e) => new Operators.Repeat(@editor, this)
159 'repeat-search': (e) => new Motions.RepeatSearch(@editor, this)
160 'repeat-search-backwards': (e) => new Motions.RepeatSearch(@editor, this).reversed()
161 'move-to-mark': (e) => new Motions.MoveToMark(@editor, this)
162 'move-to-mark-literal': (e) => new Motions.MoveToMark(@editor, this, false)
163 'mark': (e) => new Operators.Mark(@editor, this)
164 'find': (e) => new Motions.Find(@editor, this)
165 'find-backwards': (e) => new Motions.Find(@editor, this).reverse()
166 'till': (e) => new Motions.Till(@editor, this)
167 'till-backwards': (e) => new Motions.Till(@editor, this).reverse()
168 'repeat-find': (e) => @currentFind.repeat() if @currentFind?
169 'repeat-find-reverse': (e) => @currentFind.repeat(reverse: true) if @currentFind?
170 'replace': (e) => new Operators.Replace(@editor, this)
171 'search': (e) => new Motions.Search(@editor, this)
172 'reverse-search': (e) => (new Motions.Search(@editor, this)).reversed()
173 'search-current-word': (e) => new Motions.SearchCurrentWord(@editor, this)
174 'bracket-matching-motion': (e) => new Motions.BracketMatchingMotion(@editor, this)
175 'reverse-search-current-word': (e) => (new Motions.SearchCurrentWord(@editor, this)).reversed()
176
177 # Private: Register multiple command handlers via an {Object} that maps
178 # command names to command handler functions.
179 #
180 # Prefixes the given command names with 'vim-mode:' to reduce redundancy in
181 # the provided object.
182 registerCommands: (commands) ->
183 for commandName, fn of commands
184 do (fn) =>
185 @subscriptions.add(atom.commands.add(@editorElement, "vim-mode:#{commandName}", fn))
186
187 # Private: Register multiple Operators via an {Object} that
188 # maps command names to functions that return operations to push.
189 #
190 # Prefixes the given command names with 'vim-mode:' to reduce redundancy in
191 # the given object.
192 registerOperationCommands: (operationCommands) ->
193 commands = {}
194 for commandName, operationFn of operationCommands
195 do (operationFn) =>
196 commands[commandName] = (event) => @pushOperations(operationFn(event))
197 @registerCommands(commands)
198
199 # Private: Push the given operations onto the operation stack, then process
200 # it.
201 pushOperations: (operations) ->
202 return unless operations?
203 operations = [operations] unless _.isArray(operations)
204
205 for operation in operations
206 # Motions in visual mode perform their selections.
207 if @mode is 'visual' and (operation instanceof Motions.Motion or operation instanceof TextObjects.TextObject)
208 operation.execute = operation.select
209
210 # if we have started an operation that responds to canComposeWith check if it can compose
211 # with the operation we're going to push onto the stack
212 if (topOp = @topOperation())? and topOp.canComposeWith? and not topOp.canComposeWith(operation)
213 @resetCommandMode()
214 @emitter.emit('failed-to-compose')
215 break
216
217 @opStack.push(operation)
218
219 # If we've received an operator in visual mode, mark the current
220 # selection as the motion to operate on.
221 if @mode is 'visual' and operation instanceof Operators.Operator
222 @opStack.push(new Motions.CurrentSelection(@editor, this))
223
224 @processOpStack()
225
226 onDidFailToCompose: (fn) ->
227 @emitter.on('failed-to-compose', fn)
228
229 onDidDestroy: (fn) ->
230 @emitter.on('did-destroy', fn)
231
232 # Private: Removes all operations from the stack.
233 #
234 # Returns nothing.
235 clearOpStack: ->
236 @opStack = []
237
238 undo: ->
239 @editor.undo()
240 @activateCommandMode()
241
242 # Private: Processes the command if the last operation is complete.
243 #
244 # Returns nothing.
245 processOpStack: ->
246 unless @opStack.length > 0
247 return
248
249 unless @topOperation().isComplete()
250 if @mode is 'command' and @topOperation() instanceof Operators.Operator
251 @activateOperatorPendingMode()
252 return
253
254 poppedOperation = @opStack.pop()
255 if @opStack.length
256 try
257 @topOperation().compose(poppedOperation)
258 @processOpStack()
259 catch e
260 if (e instanceof Operators.OperatorError) or (e instanceof Motions.MotionError)
261 @resetCommandMode()
262 else
263 throw e
264 else
265 @history.unshift(poppedOperation) if poppedOperation.isRecordable()
266 poppedOperation.execute()
267
268 # Private: Fetches the last operation.
269 #
270 # Returns the last operation.
271 topOperation: ->
272 _.last @opStack
273
274 # Private: Fetches the value of a given register.
275 #
276 # name - The name of the register to fetch.
277 #
278 # Returns the value of the given register or undefined if it hasn't
279 # been set.
280 getRegister: (name) ->
281 if name in ['*', '+']
282 text = atom.clipboard.read()
283 type = Utils.copyType(text)
284 {text, type}
285 else if name is '%'
286 text = @editor.getURI()
287 type = Utils.copyType(text)
288 {text, type}
289 else if name is "_" # Blackhole always returns nothing
290 text = ''
291 type = Utils.copyType(text)
292 {text, type}
293 else
294 @globalVimState.registers[name.toLowerCase()]
295
296 # Private: Fetches the value of a given mark.
297 #
298 # name - The name of the mark to fetch.
299 #
300 # Returns the value of the given mark or undefined if it hasn't
301 # been set.
302 getMark: (name) ->
303 if @marks[name]
304 @marks[name].getBufferRange().start
305 else
306 undefined
307
308 # Private: Sets the value of a given register.
309 #
310 # name - The name of the register to fetch.
311 # value - The value to set the register to.
312 #
313 # Returns nothing.
314 setRegister: (name, value) ->
315 if name in ['*', '+']
316 atom.clipboard.write(value.text)
317 else if name is '_'
318 # Blackhole register, nothing to do
319 else if /^[A-Z]$/.test(name)
320 @appendRegister(name.toLowerCase(), value)
321 else
322 @globalVimState.registers[name] = value
323
324
325 # Private: append a value into a given register
326 # like setRegister, but appends the value
327 appendRegister: (name, value) ->
328 register = @globalVimState.registers[name] ?=
329 type: 'character'
330 text: ""
331 if register.type is 'linewise' and value.type isnt 'linewise'
332 register.text += value.text + '\n'
333 else if register.type isnt 'linewise' and value.type is 'linewise'
334 register.text += '\n' + value.text
335 register.type = 'linewise'
336 else
337 register.text += value.text
338
339 # Private: Sets the value of a given mark.
340 #
341 # name - The name of the mark to fetch.
342 # pos {Point} - The value to set the mark to.
343 #
344 # Returns nothing.
345 setMark: (name, pos) ->
346 # check to make sure name is in [a-z] or is `
347 if (charCode = name.charCodeAt(0)) >= 96 and charCode <= 122
348 marker = @editor.markBufferRange(new Range(pos, pos), {invalidate: 'never', persistent: false})
349 @marks[name] = marker
350
351 # Public: Append a search to the search history.
352 #
353 # Motions.Search - The confirmed search motion to append
354 #
355 # Returns nothing
356 pushSearchHistory: (search) ->
357 @globalVimState.searchHistory.unshift search
358
359 # Public: Get the search history item at the given index.
360 #
361 # index - the index of the search history item
362 #
363 # Returns a search motion
364 getSearchHistoryItem: (index = 0) ->
365 @globalVimState.searchHistory[index]
366
367 ##############################################################################
368 # Mode Switching
369 ##############################################################################
370
371 # Private: Used to enable command mode.
372 #
373 # Returns nothing.
374 activateCommandMode: ->
375 @deactivateInsertMode()
376 @deactivateVisualMode()
377
378 @mode = 'command'
379 @submode = null
380
381 @changeModeClass('command-mode')
382
383 @clearOpStack()
384 selection.clear(autoscroll: false) for selection in @editor.getSelections()
385 for cursor in @editor.getCursors()
386 if cursor.isAtEndOfLine() and not cursor.isAtBeginningOfLine()
387 cursor.moveLeft()
388
389 @updateStatusBar()
390
391 # Private: Used to enable insert mode.
392 #
393 # Returns nothing.
394 activateInsertMode: ->
395 @mode = 'insert'
396 @editorElement.component.setInputEnabled(true)
397 @setInsertionCheckpoint()
398 @submode = null
399 @changeModeClass('insert-mode')
400 @updateStatusBar()
401
402 setInsertionCheckpoint: ->
403 @insertionCheckpoint = @editor.createCheckpoint() unless @insertionCheckpoint?
404
405 deactivateInsertMode: ->
406 return unless @mode in [null, 'insert']
407 @editorElement.component.setInputEnabled(false)
408 @editor.groupChangesSinceCheckpoint(@insertionCheckpoint)
409 changes = getChangesSinceCheckpoint(@editor.buffer, @insertionCheckpoint)
410 item = @inputOperator(@history[0])
411 @insertionCheckpoint = null
412 if item?
413 item.confirmChanges(changes)
414 for cursor in @editor.getCursors()
415 cursor.moveLeft() unless cursor.isAtBeginningOfLine()
416
417 deactivateVisualMode: ->
418 return unless @mode is 'visual'
419 for selection in @editor.getSelections()
420 selection.cursor.moveLeft() unless (selection.isEmpty() or selection.isReversed())
421
422 # Private: Get the input operator that needs to be told about about the
423 # typed undo transaction in a recently completed operation, if there
424 # is one.
425 inputOperator: (item) ->
426 return item unless item?
427 return item if item.inputOperator?()
428 return item.composedObject if item.composedObject?.inputOperator?()
429
430 # Private: Used to enable visual mode.
431 #
432 # type - One of 'characterwise', 'linewise' or 'blockwise'
433 #
434 # Returns nothing.
435 activateVisualMode: (type) ->
436 # Already in 'visual', this means one of following command is
437 # executed within `vim-mode.visual-mode`
438 # * activate-blockwise-visual-mode
439 # * activate-characterwise-visual-mode
440 # * activate-linewise-visual-mode
441 if @mode is 'visual'
442 if @submode is type
443 @activateCommandMode()
444 return
445
446 @submode = type
447 if @submode is 'linewise'
448 for selection in @editor.getSelections()
449 # Keep original range as marker's property to get back
450 # to characterwise.
451 # Since selectLine lost original cursor column.
452 originalRange = selection.getBufferRange()
453 selection.marker.setProperties({originalRange})
454 [start, end] = selection.getBufferRowRange()
455 selection.selectLine(row) for row in [start..end]
456
457 else if @submode in ['characterwise', 'blockwise']
458 # Currently, 'blockwise' is not yet implemented.
459 # So treat it as characterwise.
460 # Recover original range.
461 for selection in @editor.getSelections()
462 {originalRange} = selection.marker.getProperties()
463 if originalRange
464 [startRow, endRow] = selection.getBufferRowRange()
465 originalRange.start.row = startRow
466 originalRange.end.row = endRow
467 selection.setBufferRange(originalRange)
468 else
469 @deactivateInsertMode()
470 @mode = 'visual'
471 @submode = type
472 @changeModeClass('visual-mode')
473
474 if @submode is 'linewise'
475 @editor.selectLinesContainingCursors()
476 else if @editor.getSelectedText() is ''
477 @editor.selectRight()
478
479 @updateStatusBar()
480
481 # Private: Used to re-enable visual mode
482 resetVisualMode: ->
483 @activateVisualMode(@submode)
484
485 # Private: Used to enable operator-pending mode.
486 activateOperatorPendingMode: ->
487 @deactivateInsertMode()
488 @mode = 'operator-pending'
489 @submodule = null
490 @changeModeClass('operator-pending-mode')
491
492 @updateStatusBar()
493
494 changeModeClass: (targetMode) ->
495 for mode in ['command-mode', 'insert-mode', 'visual-mode', 'operator-pending-mode']
496 if mode is targetMode
497 @editorElement.classList.add(mode)
498 else
499 @editorElement.classList.remove(mode)
500
501 # Private: Resets the command mode back to it's initial state.
502 #
503 # Returns nothing.
504 resetCommandMode: ->
505 @clearOpStack()
506 @editor.clearSelections()
507 @activateCommandMode()
508
509 # Private: A generic way to create a Register prefix based on the event.
510 #
511 # e - The event that triggered the Register prefix.
512 #
513 # Returns nothing.
514 registerPrefix: (e) ->
515 keyboardEvent = e.originalEvent?.originalEvent ? e.originalEvent
516 name = atom.keymaps.keystrokeForKeyboardEvent(keyboardEvent)
517 if name.lastIndexOf('shift-', 0) is 0
518 name = name.slice(6)
519 new Prefixes.Register(name)
520
521 # Private: A generic way to create a Number prefix based on the event.
522 #
523 # e - The event that triggered the Number prefix.
524 #
525 # Returns nothing.
526 repeatPrefix: (e) ->
527 keyboardEvent = e.originalEvent?.originalEvent ? e.originalEvent
528 num = parseInt(atom.keymaps.keystrokeForKeyboardEvent(keyboardEvent))
529 if @topOperation() instanceof Prefixes.Repeat
530 @topOperation().addDigit(num)
531 else
532 if num is 0
533 e.abortKeyBinding()
534 else
535 @pushOperations(new Prefixes.Repeat(num))
536
537 reverseSelections: ->
538 for selection in @editor.getSelections()
539 reversed = not selection.isReversed()
540 selection.setBufferRange(selection.getBufferRange(), {reversed})
541
542 # Private: Figure out whether or not we are in a repeat sequence or we just
543 # want to move to the beginning of the line. If we are within a repeat
544 # sequence, we pass control over to @repeatPrefix.
545 #
546 # e - The triggered event.
547 #
548 # Returns new motion or nothing.
549 moveOrRepeat: (e) ->
550 if @topOperation() instanceof Prefixes.Repeat
551 @repeatPrefix(e)
552 null
553 else
554 new Motions.MoveToBeginningOfLine(@editor, this)
555
556 # Private: A generic way to handle Operators that can be repeated for
557 # their linewise form.
558 #
559 # constructor - The constructor of the operator.
560 #
561 # Returns nothing.
562 linewiseAliasedOperator: (constructor) ->
563 if @isOperatorPending(constructor)
564 new Motions.MoveToRelativeLine(@editor, this)
565 else
566 new constructor(@editor, this)
567
568 # Private: Check if there is a pending operation of a certain type, or
569 # if there is any pending operation, if no type given.
570 #
571 # constructor - The constructor of the object type you're looking for.
572 #
573 isOperatorPending: (constructor) ->
574 if constructor?
575 for op in @opStack
576 return op if op instanceof constructor
577 false
578 else
579 @opStack.length > 0
580
581 updateStatusBar: ->
582 @statusBarManager.update(@mode, @submode)
583
584# This uses private APIs and may break if TextBuffer is refactored.
585# Package authors - copy and paste this code at your own risk.
586getChangesSinceCheckpoint = (buffer, checkpoint) ->
587 {history} = buffer
588
589 if (index = history.getCheckpointIndex(checkpoint))?
590 history.undoStack.slice(index)
591 else
592 []