mirror of
https://github.com/atom/atom.git
synced 2026-01-24 14:28:14 -05:00
742 lines
25 KiB
CoffeeScript
742 lines
25 KiB
CoffeeScript
{View, $$} = require 'space-pen'
|
|
Buffer = require 'buffer'
|
|
CompositeCursor = require 'composite-cursor'
|
|
CompositeSelection = require 'composite-selection'
|
|
Gutter = require 'gutter'
|
|
Renderer = require 'renderer'
|
|
Point = require 'point'
|
|
Range = require 'range'
|
|
|
|
$ = require 'jquery'
|
|
_ = require 'underscore'
|
|
|
|
module.exports =
|
|
class Editor extends View
|
|
@idCounter: 1
|
|
|
|
@content: (params) ->
|
|
@div class: @classes(params), tabindex: -1, =>
|
|
@input class: 'hidden-input', outlet: 'hiddenInput'
|
|
@div class: 'flexbox', =>
|
|
@subview 'gutter', new Gutter
|
|
@div class: 'scroll-view', outlet: 'scrollView', =>
|
|
@div class: 'lines', outlet: 'lines', =>
|
|
@div class: 'vertical-scrollbar', outlet: 'verticalScrollbar', =>
|
|
@div outlet: 'verticalScrollbarContent'
|
|
|
|
@classes: ({mini}) ->
|
|
classes = ['editor']
|
|
classes.push 'mini' if mini
|
|
classes.join(' ')
|
|
|
|
vScrollMargin: 2
|
|
hScrollMargin: 10
|
|
softWrap: false
|
|
lineHeight: null
|
|
charWidth: null
|
|
charHeight: null
|
|
cursor: null
|
|
selection: null
|
|
buffer: null
|
|
highlighter: null
|
|
renderer: null
|
|
autoIndent: null
|
|
lineCache: null
|
|
isFocused: false
|
|
softTabs: true
|
|
tabText: ' '
|
|
editSessions: null
|
|
attached: false
|
|
|
|
@deserialize: (viewState, rootView) ->
|
|
viewState = _.clone(viewState)
|
|
viewState.editSessions = viewState.editSessions.map (editSession) ->
|
|
editSession = _.clone(editSession)
|
|
editSession.buffer = Buffer.deserialize(editSession.buffer, rootView.project)
|
|
editSession
|
|
|
|
new Editor(viewState)
|
|
|
|
initialize: ({editSessions, activeEditSessionIndex, buffer, isFocused, @mini}) ->
|
|
requireStylesheet 'editor.css'
|
|
requireStylesheet 'theme/twilight.css'
|
|
|
|
@id = Editor.idCounter++
|
|
@bindKeys()
|
|
@autoIndent = true
|
|
@buildCursorAndSelection()
|
|
@handleEvents()
|
|
|
|
@editSessions = editSessions ? []
|
|
if activeEditSessionIndex?
|
|
@loadEditSession(activeEditSessionIndex)
|
|
else if buffer?
|
|
@setBuffer(buffer)
|
|
else
|
|
@setBuffer(new Buffer)
|
|
|
|
@isFocused = isFocused if isFocused?
|
|
|
|
serialize: ->
|
|
@saveCurrentEditSession()
|
|
{ viewClass: "Editor", editSessions: @serializeEditSessions(), @activeEditSessionIndex, @isFocused }
|
|
|
|
serializeEditSessions: ->
|
|
@editSessions.map (session) ->
|
|
session = _.clone(session)
|
|
session.buffer = session.buffer.serialize()
|
|
session
|
|
|
|
copy: ->
|
|
Editor.deserialize(@serialize(), @rootView())
|
|
|
|
bindKeys: ->
|
|
editorBindings =
|
|
'save': @save
|
|
'move-right': @moveCursorRight
|
|
'move-left': @moveCursorLeft
|
|
'move-down': @moveCursorDown
|
|
'move-up': @moveCursorUp
|
|
'move-to-next-word': @moveCursorToNextWord
|
|
'move-to-previous-word': @moveCursorToPreviousWord
|
|
'select-right': @selectRight
|
|
'select-left': @selectLeft
|
|
'select-up': @selectUp
|
|
'select-down': @selectDown
|
|
'newline': @insertNewline
|
|
'tab': @insertTab
|
|
'indent-selected-rows': @indentSelectedRows
|
|
'outdent-selected-rows': @outdentSelectedRows
|
|
'backspace': @backspace
|
|
'backspace-to-beginning-of-word': @backspaceToBeginningOfWord
|
|
'delete': @delete
|
|
'delete-to-end-of-word': @deleteToEndOfWord
|
|
'cut-to-end-of-line': @cutToEndOfLine
|
|
'cut': @cutSelection
|
|
'copy': @copySelection
|
|
'paste': @paste
|
|
'undo': @undo
|
|
'redo': @redo
|
|
'toggle-soft-wrap': @toggleSoftWrap
|
|
'fold-selection': @foldSelection
|
|
'split-left': @splitLeft
|
|
'split-right': @splitRight
|
|
'split-up': @splitUp
|
|
'split-down': @splitDown
|
|
'close': @close
|
|
'show-next-buffer': @loadNextEditSession
|
|
'show-previous-buffer': @loadPreviousEditSession
|
|
|
|
'move-to-top': @moveCursorToTop
|
|
'move-to-bottom': @moveCursorToBottom
|
|
'move-to-beginning-of-line': @moveCursorToBeginningOfLine
|
|
'move-to-end-of-line': @moveCursorToEndOfLine
|
|
'move-to-first-character-of-line': @moveCursorToFirstCharacterOfLine
|
|
'move-to-beginning-of-word': @moveCursorToBeginningOfWord
|
|
'move-to-end-of-word': @moveCursorToEndOfWord
|
|
'select-to-top': @selectToTop
|
|
'select-to-bottom': @selectToBottom
|
|
'select-to-end-of-line': @selectToEndOfLine
|
|
'select-to-beginning-of-line': @selectToBeginningOfLine
|
|
'select-to-end-of-word': @selectToEndOfWord
|
|
'select-to-beginning-of-word': @selectToBeginningOfWord
|
|
'select-all': @selectAll
|
|
|
|
for name, method of editorBindings
|
|
do (name, method) =>
|
|
@on name, => method.call(this); false
|
|
|
|
buildCursorAndSelection: ->
|
|
@compositeSelection = new CompositeSelection(this)
|
|
@compositeCursor = new CompositeCursor(this)
|
|
|
|
addCursorAtScreenPosition: (screenPosition) ->
|
|
@compositeCursor.addCursorAtScreenPosition(screenPosition)
|
|
|
|
addCursorAtBufferPosition: (bufferPosition) ->
|
|
@compositeCursor.addCursorAtBufferPosition(bufferPosition)
|
|
|
|
addSelectionForCursor: (cursor) ->
|
|
@compositeSelection.addSelectionForCursor(cursor)
|
|
|
|
handleEvents: ->
|
|
@on 'focus', =>
|
|
@hiddenInput.focus()
|
|
false
|
|
|
|
@hiddenInput.on 'focus', =>
|
|
@rootView()?.editorFocused(this)
|
|
@isFocused = true
|
|
@addClass 'focused'
|
|
|
|
@hiddenInput.on 'focusout', =>
|
|
@isFocused = false
|
|
@removeClass 'focused'
|
|
|
|
@lines.on 'mousedown', '.fold-placeholder', (e) =>
|
|
@destroyFold($(e.currentTarget).attr('foldId'))
|
|
false
|
|
|
|
@lines.on 'mousedown', (e) =>
|
|
clickCount = e.originalEvent.detail
|
|
|
|
if clickCount == 1
|
|
screenPosition = @screenPositionFromMouseEvent(e)
|
|
if e.metaKey
|
|
@addCursorAtScreenPosition(screenPosition)
|
|
else if e.shiftKey
|
|
@selectToScreenPosition(@screenPositionFromMouseEvent(e))
|
|
else
|
|
@setCursorScreenPosition(screenPosition)
|
|
else if clickCount == 2
|
|
if e.shiftKey
|
|
@compositeSelection.getLastSelection().expandOverWord()
|
|
else
|
|
@compositeSelection.getLastSelection().selectWord()
|
|
else if clickCount >= 3
|
|
if e.shiftKey
|
|
@compositeSelection.getLastSelection().expandOverLine()
|
|
else
|
|
@compositeSelection.getLastSelection().selectLine()
|
|
|
|
@selectOnMousemoveUntilMouseup()
|
|
|
|
@on "textInput", (e) =>
|
|
@insertText(e.originalEvent.data)
|
|
false
|
|
|
|
@scrollView.on 'mousewheel', (e) =>
|
|
e = e.originalEvent
|
|
if e.wheelDeltaY
|
|
newEvent = document.createEvent("WheelEvent");
|
|
newEvent.initWebKitWheelEvent(0, e.wheelDeltaY, e.view, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey)
|
|
@verticalScrollbar.get(0).dispatchEvent(newEvent)
|
|
false
|
|
|
|
@verticalScrollbar.on 'scroll', =>
|
|
@scrollTop(@verticalScrollbar.scrollTop(), adjustVerticalScrollbar: false)
|
|
|
|
@scrollView.on 'scroll', =>
|
|
if @scrollView.scrollLeft() == 0
|
|
@gutter.removeClass('drop-shadow')
|
|
else
|
|
@gutter.addClass('drop-shadow')
|
|
|
|
$(window).on "resize", =>
|
|
@updateVisibleLines()
|
|
|
|
afterAttach: (onDom) ->
|
|
return if @attached or not onDom
|
|
@attached = true
|
|
@subscribeToFontSize()
|
|
@calculateDimensions()
|
|
@setMaxLineLength() if @softWrap
|
|
@prepareForVerticalScrolling()
|
|
@setScrollPositionFromActiveEditSession()
|
|
@renderVisibleLines()
|
|
# TODO: The redundant assignment of scrollLeft below is needed because the lines weren't render
|
|
# rendered when we called setScrollPositionFromActiveEditSession above. Remove this when we fix
|
|
# that problem by setting the width of the lines container based on the max line width
|
|
@scrollView.scrollLeft(@getActiveEditSession().scrollLeft ? 0)
|
|
@hiddenInput.width(@charWidth)
|
|
@focus() if @isFocused
|
|
@trigger 'editor-open', [this]
|
|
|
|
rootView: ->
|
|
@parents('#root-view').view()
|
|
|
|
selectOnMousemoveUntilMouseup: ->
|
|
moveHandler = (e) => @selectToScreenPosition(@screenPositionFromMouseEvent(e))
|
|
@on 'mousemove', moveHandler
|
|
$(document).one 'mouseup', =>
|
|
@off 'mousemove', moveHandler
|
|
reverse = @compositeSelection.getLastSelection().isReversed()
|
|
@compositeSelection.mergeIntersectingSelections({reverse})
|
|
@syncCursorAnimations()
|
|
|
|
prepareForVerticalScrolling: ->
|
|
linesHeight = @lineHeight * @screenLineCount()
|
|
@verticalScrollbarContent.height(linesHeight)
|
|
@lines.css('padding-bottom', linesHeight)
|
|
|
|
scrollTop: (scrollTop, options) ->
|
|
return @cachedScrollTop or 0 unless scrollTop?
|
|
|
|
scrollTop = Math.max(0, scrollTop)
|
|
return if scrollTop == @cachedScrollTop
|
|
@cachedScrollTop = scrollTop
|
|
|
|
@updateVisibleLines() if @attached
|
|
|
|
transform = "translate3d(0px, #{-scrollTop}px, 0px)"
|
|
@lines.css('-webkit-transform', transform)
|
|
@gutter.lineNumbers.css('-webkit-transform', transform)
|
|
if options?.adjustVerticalScrollbar ? true
|
|
@verticalScrollbar.scrollTop(scrollTop)
|
|
|
|
scrollBottom: ->
|
|
@scrollTop() + @scrollView.height()
|
|
|
|
renderVisibleLines: ->
|
|
@lineCache = []
|
|
@lines.find('.line').remove()
|
|
|
|
@firstRenderedScreenRow = -1
|
|
@lastRenderedScreenRow = -1
|
|
@updateVisibleLines()
|
|
|
|
updateVisibleLines: ->
|
|
firstVisibleScreenRow = @getFirstVisibleScreenRow()
|
|
lastVisibleScreenRow = @getLastVisibleScreenRow()
|
|
|
|
@gutter.renderLineNumbers(firstVisibleScreenRow, lastVisibleScreenRow)
|
|
|
|
if firstVisibleScreenRow > @firstRenderedScreenRow
|
|
@removeLineElements(@firstRenderedScreenRow, firstVisibleScreenRow - 1)
|
|
|
|
if lastVisibleScreenRow < @lastRenderedScreenRow
|
|
@removeLineElements(lastVisibleScreenRow + 1, @lastRenderedScreenRow)
|
|
|
|
if firstVisibleScreenRow < @firstRenderedScreenRow
|
|
newLinesStartRow = firstVisibleScreenRow
|
|
newLinesEndRow = Math.min(@firstRenderedScreenRow - 1, lastVisibleScreenRow)
|
|
lineElements = @buildLineElements(newLinesStartRow, newLinesEndRow)
|
|
@insertLineElements(newLinesStartRow, lineElements)
|
|
|
|
if lastVisibleScreenRow > @lastRenderedScreenRow
|
|
newLinesStartRow = Math.max(@lastRenderedScreenRow + 1, firstVisibleScreenRow)
|
|
newLinesEndRow = lastVisibleScreenRow
|
|
lineElements = @buildLineElements(newLinesStartRow, newLinesEndRow)
|
|
@insertLineElements(newLinesStartRow, lineElements)
|
|
|
|
if firstVisibleScreenRow != @firstRenderedScreenRow
|
|
paddingTop = firstVisibleScreenRow * @lineHeight
|
|
@lines.css('padding-top', paddingTop)
|
|
@gutter.lineNumbers.css('padding-top', paddingTop)
|
|
@firstRenderedScreenRow = firstVisibleScreenRow
|
|
|
|
if lastVisibleScreenRow != @lastRenderedScreenRow
|
|
paddingBottom = (@getLastScreenRow() - lastVisibleScreenRow) * @lineHeight
|
|
@lines.css('padding-bottom', paddingBottom)
|
|
@gutter.lineNumbers.css('padding-bottom', paddingBottom)
|
|
@lastRenderedScreenRow = lastVisibleScreenRow
|
|
|
|
getFirstVisibleScreenRow: ->
|
|
Math.floor(@scrollTop() / @lineHeight)
|
|
|
|
getLastVisibleScreenRow: ->
|
|
Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1
|
|
|
|
getScreenLines: ->
|
|
@renderer.getLines()
|
|
|
|
linesForRows: (start, end) ->
|
|
@renderer.linesForRows(start, end)
|
|
|
|
screenLineCount: ->
|
|
@renderer.lineCount()
|
|
|
|
getLastScreenRow: ->
|
|
@screenLineCount() - 1
|
|
|
|
setBuffer: (buffer) ->
|
|
if @buffer
|
|
@saveCurrentEditSession()
|
|
@unsubscribeFromBuffer()
|
|
|
|
@buffer = buffer
|
|
@trigger 'editor-path-change'
|
|
@buffer.on "path-change.editor#{@id}", => @trigger 'editor-path-change'
|
|
|
|
@renderer = new Renderer(@buffer, { maxLineLength: @calcMaxLineLength(), tabText: @tabText })
|
|
if @attached
|
|
@prepareForVerticalScrolling()
|
|
@renderVisibleLines()
|
|
|
|
@loadEditSessionForBuffer(@buffer)
|
|
|
|
@buffer.on "change.editor#{@id}", (e) => @handleBufferChange(e)
|
|
@renderer.on 'change', (e) => @handleRendererChange(e)
|
|
|
|
editSessionForBuffer: (buffer) ->
|
|
for editSession, index in @editSessions
|
|
return [editSession, index] if editSession.buffer == buffer
|
|
[undefined, -1]
|
|
|
|
loadEditSessionForBuffer: (buffer) ->
|
|
[editSession, index] = @editSessionForBuffer(buffer)
|
|
if editSession
|
|
@activeEditSessionIndex = index
|
|
else
|
|
@editSessions.push({ buffer })
|
|
@activeEditSessionIndex = @editSessions.length - 1
|
|
@loadEditSession()
|
|
|
|
loadNextEditSession: ->
|
|
nextIndex = (@activeEditSessionIndex + 1) % @editSessions.length
|
|
@loadEditSession(nextIndex)
|
|
|
|
loadPreviousEditSession: ->
|
|
previousIndex = @activeEditSessionIndex - 1
|
|
previousIndex = @editSessions.length - 1 if previousIndex < 0
|
|
@loadEditSession(previousIndex)
|
|
|
|
loadEditSession: (index=@activeEditSessionIndex) ->
|
|
editSession = @editSessions[index]
|
|
throw new Error("Edit session not found") unless editSession
|
|
@setBuffer(editSession.buffer) unless @buffer == editSession.buffer
|
|
@activeEditSessionIndex = index
|
|
@setScrollPositionFromActiveEditSession() if @attached
|
|
@setCursorScreenPosition(editSession.cursorScreenPosition ? [0, 0])
|
|
|
|
getActiveEditSession: ->
|
|
@editSessions[@activeEditSessionIndex]
|
|
|
|
setScrollPositionFromActiveEditSession: ->
|
|
editSession = @getActiveEditSession()
|
|
@scrollTop(editSession.scrollTop ? 0)
|
|
@scrollView.scrollLeft(editSession.scrollLeft ? 0)
|
|
|
|
saveCurrentEditSession: ->
|
|
@editSessions[@activeEditSessionIndex] =
|
|
buffer: @buffer
|
|
cursorScreenPosition: @getCursorScreenPosition()
|
|
scrollTop: @scrollTop()
|
|
scrollLeft: @scrollView.scrollLeft()
|
|
|
|
handleBufferChange: (e) ->
|
|
@compositeCursor.handleBufferChange(e)
|
|
@compositeSelection.handleBufferChange(e)
|
|
|
|
handleRendererChange: (e) ->
|
|
oldScreenRange = e.oldRange
|
|
newScreenRange = e.newRange
|
|
|
|
@compositeCursor.updateBufferPosition() unless e.bufferChanged
|
|
|
|
if @attached
|
|
unless newScreenRange.isSingleLine() and newScreenRange.coversSameRows(oldScreenRange)
|
|
@gutter.renderLineNumbers(@getFirstVisibleScreenRow(), @getLastVisibleScreenRow())
|
|
|
|
lineElements = @buildLineElements(newScreenRange.start.row, newScreenRange.end.row)
|
|
@replaceLineElements(oldScreenRange.start.row, oldScreenRange.end.row, lineElements)
|
|
@verticalScrollbarContent.height(@lineHeight * @screenLineCount())
|
|
|
|
rowDelta = newScreenRange.end.row - oldScreenRange.end.row
|
|
@lastRenderedScreenRow += rowDelta
|
|
@updateVisibleLines() if rowDelta < 0
|
|
|
|
buildLineElements: (startRow, endRow) ->
|
|
charWidth = @charWidth
|
|
charHeight = @charHeight
|
|
lines = @renderer.linesForRows(startRow, endRow)
|
|
$$ ->
|
|
for line in lines
|
|
@div class: 'line', =>
|
|
appendNbsp = true
|
|
for token in line.tokens
|
|
if token.type is 'fold-placeholder'
|
|
@span ' ', class: 'fold-placeholder', style: "width: #{3 * charWidth}px; height: #{charHeight}px;", 'foldId': token.fold.id, =>
|
|
@div class: "ellipsis", => @raw "…"
|
|
else
|
|
appendNbsp = false
|
|
@span { class: token.type.replace('.', ' ') }, token.value
|
|
@raw ' ' if appendNbsp
|
|
|
|
insertLineElements: (row, lineElements) ->
|
|
@spliceLineElements(row, 0, lineElements)
|
|
|
|
replaceLineElements: (startRow, endRow, lineElements) ->
|
|
@spliceLineElements(startRow, endRow - startRow + 1, lineElements)
|
|
|
|
removeLineElements: (startRow, endRow) ->
|
|
@spliceLineElements(startRow, endRow - startRow + 1)
|
|
|
|
spliceLineElements: (startScreenRow, rowCount, lineElements) ->
|
|
if startScreenRow < @firstRenderedScreenRow
|
|
startRow = 0
|
|
else
|
|
startRow = startScreenRow - @firstRenderedScreenRow
|
|
endRow = startRow + rowCount
|
|
|
|
elementToInsertBefore = @lineCache[startRow]
|
|
elementsToReplace = @lineCache[startRow...endRow]
|
|
@lineCache[startRow...endRow] = lineElements?.toArray() or []
|
|
|
|
lines = @lines[0]
|
|
if lineElements
|
|
fragment = document.createDocumentFragment()
|
|
lineElements.each -> fragment.appendChild(this)
|
|
if elementToInsertBefore
|
|
lines.insertBefore(fragment, elementToInsertBefore)
|
|
else
|
|
lines.appendChild(fragment)
|
|
|
|
elementsToReplace.forEach (element) =>
|
|
lines.removeChild(element)
|
|
|
|
getLineElement: (row) ->
|
|
@lineCache[row]
|
|
|
|
toggleSoftWrap: ->
|
|
@setSoftWrap(not @softWrap)
|
|
|
|
calcMaxLineLength: ->
|
|
if @softWrap
|
|
Math.floor(@scrollView.width() / @charWidth)
|
|
else
|
|
Infinity
|
|
|
|
setMaxLineLength: (maxLineLength) ->
|
|
maxLineLength ?= @calcMaxLineLength()
|
|
@renderer.setMaxLineLength(maxLineLength) if maxLineLength
|
|
|
|
createFold: (range) ->
|
|
@renderer.createFold(range)
|
|
|
|
setSoftWrap: (@softWrap, maxLineLength=undefined) ->
|
|
@setMaxLineLength(maxLineLength) if @attached
|
|
if @softWrap
|
|
@addClass 'soft-wrap'
|
|
@_setMaxLineLength = => @setMaxLineLength()
|
|
$(window).on 'resize', @_setMaxLineLength
|
|
else
|
|
@removeClass 'soft-wrap'
|
|
$(window).off 'resize', @_setMaxLineLength
|
|
|
|
save: ->
|
|
if not @buffer.getPath()
|
|
path = $native.saveDialog()
|
|
return if not path
|
|
@buffer.saveAs(path)
|
|
else
|
|
@buffer.save()
|
|
|
|
clipScreenPosition: (screenPosition, options={}) ->
|
|
@renderer.clipScreenPosition(screenPosition, options)
|
|
|
|
pixelPositionForScreenPosition: (position) ->
|
|
position = Point.fromObject(position)
|
|
{ top: position.row * @lineHeight, left: position.column * @charWidth }
|
|
|
|
pixelOffsetForScreenPosition: (position) ->
|
|
{top, left} = @pixelPositionForScreenPosition(position)
|
|
offset = @lines.offset()
|
|
{top: top + offset.top, left: left + offset.left}
|
|
|
|
screenPositionFromPixelPosition: ({top, left}) ->
|
|
screenPosition = new Point(Math.floor(top / @lineHeight), Math.floor(left / @charWidth))
|
|
|
|
screenPositionForBufferPosition: (position, options) ->
|
|
@renderer.screenPositionForBufferPosition(position, options)
|
|
|
|
bufferPositionForScreenPosition: (position, options) ->
|
|
@renderer.bufferPositionForScreenPosition(position, options)
|
|
|
|
screenRangeForBufferRange: (range) ->
|
|
@renderer.screenRangeForBufferRange(range)
|
|
|
|
bufferRangeForScreenRange: (range) ->
|
|
@renderer.bufferRangeForScreenRange(range)
|
|
|
|
bufferRowsForScreenRows: (startRow, endRow) ->
|
|
@renderer.bufferRowsForScreenRows(startRow, endRow)
|
|
|
|
screenPositionFromMouseEvent: (e) ->
|
|
{ pageX, pageY } = e
|
|
@screenPositionFromPixelPosition
|
|
top: pageY - @scrollView.offset().top + @scrollTop()
|
|
left: pageX - @scrollView.offset().left + @scrollView.scrollLeft()
|
|
|
|
calculateDimensions: ->
|
|
fragment = $('<div class="line" style="position: absolute; visibility: hidden;"><span>x</span></div>')
|
|
@lines.append(fragment)
|
|
@charWidth = fragment.width()
|
|
@charHeight = fragment.find('span').height()
|
|
@lineHeight = fragment.outerHeight()
|
|
@height(@lineHeight) if @mini
|
|
fragment.remove()
|
|
|
|
subscribeToFontSize: ->
|
|
return unless rootView = @rootView()
|
|
@setFontSize(rootView.getFontSize())
|
|
rootView.on "font-size-change.editor#{@id}", =>
|
|
@setFontSize(rootView.getFontSize())
|
|
@calculateDimensions()
|
|
@compositeCursor.updateAppearance()
|
|
|
|
setFontSize: (fontSize) ->
|
|
@css('font-size', fontSize + 'px') if fontSize
|
|
|
|
getCursors: -> @compositeCursor.getCursors()
|
|
moveCursorUp: -> @compositeCursor.moveUp()
|
|
moveCursorDown: -> @compositeCursor.moveDown()
|
|
moveCursorRight: -> @compositeCursor.moveRight()
|
|
moveCursorLeft: -> @compositeCursor.moveLeft()
|
|
moveCursorToNextWord: -> @compositeCursor.moveToNextWord()
|
|
moveCursorToBeginningOfWord: -> @compositeCursor.moveToBeginningOfWord()
|
|
moveCursorToEndOfWord: -> @compositeCursor.moveToEndOfWord()
|
|
moveCursorToTop: -> @compositeCursor.moveToTop()
|
|
moveCursorToBottom: -> @compositeCursor.moveToBottom()
|
|
moveCursorToBeginningOfLine: -> @compositeCursor.moveToBeginningOfLine()
|
|
moveCursorToFirstCharacterOfLine: -> @compositeCursor.moveToFirstCharacterOfLine()
|
|
moveCursorToEndOfLine: -> @compositeCursor.moveToEndOfLine()
|
|
setCursorScreenPosition: (position) -> @compositeCursor.setScreenPosition(position)
|
|
getCursorScreenPosition: -> @compositeCursor.getCursor().getScreenPosition()
|
|
setCursorBufferPosition: (position) -> @compositeCursor.setBufferPosition(position)
|
|
getCursorBufferPosition: -> @compositeCursor.getCursor().getBufferPosition()
|
|
|
|
getSelection: (index) -> @compositeSelection.getSelection(index)
|
|
getSelections: -> @compositeSelection.getSelections()
|
|
getSelectionsOrderedByBufferPosition: -> @compositeSelection.getSelectionsOrderedByBufferPosition()
|
|
getLastSelectionInBuffer: -> @compositeSelection.getLastSelectionInBuffer()
|
|
getSelectedText: -> @compositeSelection.getSelection().getText()
|
|
setSelectionBufferRange: (bufferRange, options) -> @compositeSelection.setBufferRange(bufferRange, options)
|
|
setSelectedBufferRanges: (bufferRanges) -> @compositeSelection.setBufferRanges(bufferRanges)
|
|
addSelectionForBufferRange: (bufferRange, options) -> @compositeSelection.addSelectionForBufferRange(bufferRange, options)
|
|
selectRight: -> @compositeSelection.selectRight()
|
|
selectLeft: -> @compositeSelection.selectLeft()
|
|
selectUp: -> @compositeSelection.selectUp()
|
|
selectDown: -> @compositeSelection.selectDown()
|
|
selectToTop: -> @compositeSelection.selectToTop()
|
|
selectToBottom: -> @compositeSelection.selectToBottom()
|
|
selectAll: -> @compositeSelection.selectAll()
|
|
selectToBeginningOfLine: -> @compositeSelection.selectToBeginningOfLine()
|
|
selectToEndOfLine: -> @compositeSelection.selectToEndOfLine()
|
|
selectToBeginningOfWord: -> @compositeSelection.selectToBeginningOfWord()
|
|
selectToEndOfWord: -> @compositeSelection.selectToEndOfWord()
|
|
selectToScreenPosition: (position) -> @compositeSelection.selectToScreenPosition(position)
|
|
clearSelections: -> @compositeSelection.clearSelections()
|
|
backspace: -> @compositeSelection.backspace()
|
|
backspaceToBeginningOfWord: -> @compositeSelection.backspaceToBeginningOfWord()
|
|
delete: -> @compositeSelection.delete()
|
|
deleteToEndOfWord: -> @compositeSelection.deleteToEndOfWord()
|
|
cutToEndOfLine: -> @compositeSelection.cutToEndOfLine()
|
|
|
|
setText: (text) -> @buffer.setText(text)
|
|
getText: -> @buffer.getText()
|
|
getLastBufferRow: -> @buffer.getLastRow()
|
|
getTextInRange: (range) -> @buffer.getTextInRange(range)
|
|
getEofPosition: -> @buffer.getEofPosition()
|
|
lineForBufferRow: (row) -> @buffer.lineForRow(row)
|
|
lineLengthForBufferRow: (row) -> @buffer.lineLengthForRow(row)
|
|
rangeForBufferRow: (row) -> @buffer.rangeForRow(row)
|
|
scanInRange: (args...) -> @buffer.scanInRange(args...)
|
|
backwardsScanInRange: (args...) -> @buffer.backwardsScanInRange(args...)
|
|
|
|
insertText: (text) ->
|
|
@compositeSelection.insertText(text)
|
|
|
|
insertNewline: ->
|
|
@insertText('\n')
|
|
|
|
insertTab: ->
|
|
if @getSelection().isEmpty()
|
|
if @softTabs
|
|
@compositeSelection.insertText(@tabText)
|
|
else
|
|
@compositeSelection.insertText('\t')
|
|
else
|
|
@compositeSelection.indentSelectedRows()
|
|
|
|
indentSelectedRows: -> @compositeSelection.indentSelectedRows()
|
|
outdentSelectedRows: -> @compositeSelection.outdentSelectedRows()
|
|
|
|
cutSelection: -> @compositeSelection.cut()
|
|
copySelection: -> @compositeSelection.copy()
|
|
paste: -> @insertText($native.readFromPasteboard())
|
|
|
|
foldSelection: -> @getSelection().fold()
|
|
|
|
undo: ->
|
|
if ranges = @buffer.undo()
|
|
@setSelectedBufferRanges(ranges)
|
|
|
|
redo: ->
|
|
if ranges = @buffer.redo()
|
|
@setSelectedBufferRanges(ranges)
|
|
|
|
destroyFold: (foldId) ->
|
|
fold = @renderer.foldsById[foldId]
|
|
fold.destroy()
|
|
@setCursorBufferPosition(fold.start)
|
|
|
|
splitLeft: ->
|
|
@pane()?.splitLeft(@copy()).wrappedView
|
|
|
|
splitRight: ->
|
|
@pane()?.splitRight(@copy()).wrappedView
|
|
|
|
splitUp: ->
|
|
@pane()?.splitUp(@copy()).wrappedView
|
|
|
|
splitDown: ->
|
|
@pane()?.splitDown(@copy()).wrappedView
|
|
|
|
pane: ->
|
|
@parent('.pane').view()
|
|
|
|
close: ->
|
|
@remove() unless @mini
|
|
|
|
remove: (selector, keepData) ->
|
|
return super if keepData
|
|
|
|
@trigger 'before-remove'
|
|
@unsubscribeFromBuffer()
|
|
rootView = @rootView()
|
|
rootView?.off ".editor#{@id}"
|
|
if @pane() then @pane().remove() else super
|
|
rootView?.focus()
|
|
|
|
unsubscribeFromBuffer: ->
|
|
@buffer.off ".editor#{@id}"
|
|
@renderer.destroy()
|
|
|
|
stateForScreenRow: (row) ->
|
|
@renderer.lineForRow(row).state
|
|
|
|
getCurrentMode: ->
|
|
@buffer.getMode()
|
|
|
|
scrollTo: (pixelPosition) ->
|
|
return unless @attached
|
|
@scrollVertically(pixelPosition)
|
|
@scrollHorizontally(pixelPosition)
|
|
|
|
scrollVertically: (pixelPosition) ->
|
|
linesInView = @scrollView.height() / @lineHeight
|
|
maxScrollMargin = Math.floor((linesInView - 1) / 2)
|
|
scrollMargin = Math.min(@vScrollMargin, maxScrollMargin)
|
|
margin = scrollMargin * @lineHeight
|
|
desiredTop = pixelPosition.top - margin
|
|
desiredBottom = pixelPosition.top + @lineHeight + margin
|
|
|
|
scrollViewHeight = @scrollView.height()
|
|
if desiredBottom > @scrollTop() + scrollViewHeight
|
|
@scrollTop(desiredBottom - scrollViewHeight)
|
|
else if desiredTop < @scrollTop()
|
|
@scrollTop(desiredTop)
|
|
|
|
scrollHorizontally: (pixelPosition) ->
|
|
return if @softWrap
|
|
|
|
charsInView = @scrollView.width() / @charWidth
|
|
maxScrollMargin = Math.floor((charsInView - 1) / 2)
|
|
scrollMargin = Math.min(@hScrollMargin, maxScrollMargin)
|
|
margin = scrollMargin * @charWidth
|
|
desiredRight = pixelPosition.left + @charWidth + margin
|
|
desiredLeft = pixelPosition.left - margin
|
|
|
|
if desiredRight > @scrollView.scrollRight()
|
|
@scrollView.scrollRight(desiredRight)
|
|
else if desiredLeft < @scrollView.scrollLeft()
|
|
@scrollView.scrollLeft(desiredLeft)
|
|
|
|
syncCursorAnimations: ->
|
|
for cursor in @getCursors()
|
|
do (cursor) -> cursor.resetCursorAnimation()
|
|
|
|
logLines: ->
|
|
@renderer.logLines()
|