mirror of
https://github.com/atom/atom.git
synced 2026-01-23 05:48:10 -05:00
1591 lines
56 KiB
CoffeeScript
1591 lines
56 KiB
CoffeeScript
{View, $$} = require 'space-pen'
|
||
Buffer = require 'text-buffer'
|
||
Gutter = require 'gutter'
|
||
Point = require 'point'
|
||
Range = require 'range'
|
||
EditSession = require 'edit-session'
|
||
CursorView = require 'cursor-view'
|
||
SelectionView = require 'selection-view'
|
||
fsUtils = require 'fs-utils'
|
||
$ = require 'jquery'
|
||
_ = require 'underscore'
|
||
|
||
# Public: Represents the entire visual pane in Atom.
|
||
#
|
||
# The Editor manages the {EditSession}, which manages the file buffers.
|
||
module.exports =
|
||
class Editor extends View
|
||
@configDefaults:
|
||
fontSize: 20
|
||
showInvisibles: false
|
||
showIndentGuide: false
|
||
showLineNumbers: true
|
||
autoIndent: true
|
||
autoIndentOnPaste: false
|
||
nonWordCharacters: "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?-"
|
||
preferredLineLength: 80
|
||
|
||
@nextEditorId: 1
|
||
|
||
### Internal ###
|
||
|
||
@content: (params) ->
|
||
attributes = { class: @classes(params), tabindex: -1 }
|
||
_.extend(attributes, params.attributes) if params.attributes
|
||
@div attributes, =>
|
||
@subview 'gutter', new Gutter
|
||
@input class: 'hidden-input', outlet: 'hiddenInput'
|
||
@div class: 'scroll-view', outlet: 'scrollView', =>
|
||
@div class: 'overlayer', outlet: 'overlayer'
|
||
@div class: 'lines', outlet: 'renderedLines'
|
||
@div class: 'underlayer', outlet: 'underlayer'
|
||
@div class: 'vertical-scrollbar', outlet: 'verticalScrollbar', =>
|
||
@div outlet: 'verticalScrollbarContent'
|
||
|
||
@classes: ({mini} = {}) ->
|
||
classes = ['editor']
|
||
classes.push 'mini' if mini
|
||
classes.join(' ')
|
||
|
||
vScrollMargin: 2
|
||
hScrollMargin: 10
|
||
lineHeight: null
|
||
charWidth: null
|
||
charHeight: null
|
||
cursorViews: null
|
||
selectionViews: null
|
||
lineCache: null
|
||
isFocused: false
|
||
activeEditSession: null
|
||
attached: false
|
||
lineOverdraw: 10
|
||
pendingChanges: null
|
||
newCursors: null
|
||
newSelections: null
|
||
redrawOnReattach: false
|
||
bottomPaddingInLines: 10
|
||
|
||
### Public ###
|
||
|
||
# The constructor for setting up an `Editor` instance.
|
||
#
|
||
# editSessionOrOptions - Either an {EditSession}, or an object with one property, `mini`.
|
||
# If `mini` is `true`, a "miniature" `EditSession` is constructed.
|
||
# Typically, this is ideal for scenarios where you need an Atom editor,
|
||
# but without all the chrome, like scrollbars, gutter, _e.t.c._.
|
||
#
|
||
initialize: (editSessionOrOptions) ->
|
||
if editSessionOrOptions instanceof EditSession
|
||
editSession = editSessionOrOptions
|
||
else
|
||
{editSession, @mini} = editSessionOrOptions ? {}
|
||
|
||
requireStylesheet 'editor'
|
||
|
||
@id = Editor.nextEditorId++
|
||
@lineCache = []
|
||
@configure()
|
||
@bindKeys()
|
||
@handleEvents()
|
||
@cursorViews = []
|
||
@selectionViews = []
|
||
@pendingChanges = []
|
||
@newCursors = []
|
||
@newSelections = []
|
||
|
||
if editSession?
|
||
@edit(editSession)
|
||
else if @mini
|
||
@edit(new EditSession
|
||
buffer: new Buffer()
|
||
softWrap: false
|
||
tabLength: 2
|
||
softTabs: true
|
||
)
|
||
else
|
||
throw new Error("Must supply an EditSession or mini: true")
|
||
|
||
# Internal: Sets up the core Atom commands.
|
||
#
|
||
# Some commands are excluded from mini-editors.
|
||
bindKeys: ->
|
||
editorBindings =
|
||
'core:move-left': @moveCursorLeft
|
||
'core:move-right': @moveCursorRight
|
||
'core:select-left': @selectLeft
|
||
'core:select-right': @selectRight
|
||
'core:select-all': @selectAll
|
||
'core:backspace': @backspace
|
||
'core:delete': @delete
|
||
'core:undo': @undo
|
||
'core:redo': @redo
|
||
'core:cut': @cutSelection
|
||
'core:copy': @copySelection
|
||
'core:paste': @paste
|
||
'editor:move-to-previous-word': @moveCursorToPreviousWord
|
||
'editor:select-word': @selectWord
|
||
'editor:newline': @insertNewline
|
||
'editor:consolidate-selections': @consolidateSelections
|
||
'editor:indent': @indent
|
||
'editor:auto-indent': @autoIndent
|
||
'editor:indent-selected-rows': @indentSelectedRows
|
||
'editor:outdent-selected-rows': @outdentSelectedRows
|
||
'editor:backspace-to-beginning-of-word': @backspaceToBeginningOfWord
|
||
'editor:backspace-to-beginning-of-line': @backspaceToBeginningOfLine
|
||
'editor:delete-to-end-of-word': @deleteToEndOfWord
|
||
'editor:delete-line': @deleteLine
|
||
'editor:cut-to-end-of-line': @cutToEndOfLine
|
||
'editor:move-to-beginning-of-line': @moveCursorToBeginningOfLine
|
||
'editor:move-to-end-of-line': @moveCursorToEndOfLine
|
||
'editor:move-to-first-character-of-line': @moveCursorToFirstCharacterOfLine
|
||
'editor:move-to-beginning-of-word': @moveCursorToBeginningOfWord
|
||
'editor:move-to-end-of-word': @moveCursorToEndOfWord
|
||
'editor:move-to-beginning-of-next-word': @moveCursorToBeginningOfNextWord
|
||
'editor:select-to-end-of-line': @selectToEndOfLine
|
||
'editor:select-to-beginning-of-line': @selectToBeginningOfLine
|
||
'editor:select-to-end-of-word': @selectToEndOfWord
|
||
'editor:select-to-beginning-of-word': @selectToBeginningOfWord
|
||
'editor:select-to-beginning-of-next-word': @selectToBeginningOfNextWord
|
||
'editor:add-selection-below': @addSelectionBelow
|
||
'editor:add-selection-above': @addSelectionAbove
|
||
'editor:select-line': @selectLine
|
||
'editor:transpose': @transpose
|
||
'editor:upper-case': @upperCase
|
||
'editor:lower-case': @lowerCase
|
||
|
||
unless @mini
|
||
_.extend editorBindings,
|
||
'core:move-up': @moveCursorUp
|
||
'core:move-down': @moveCursorDown
|
||
'core:move-to-top': @moveCursorToTop
|
||
'core:move-to-bottom': @moveCursorToBottom
|
||
'core:page-down': @pageDown
|
||
'core:page-up': @pageUp
|
||
'core:select-up': @selectUp
|
||
'core:select-down': @selectDown
|
||
'core:select-to-top': @selectToTop
|
||
'core:select-to-bottom': @selectToBottom
|
||
'editor:newline-below': @insertNewlineBelow
|
||
'editor:newline-above': @insertNewlineAbove
|
||
'editor:toggle-soft-tabs': @toggleSoftTabs
|
||
'editor:toggle-soft-wrap': @toggleSoftWrap
|
||
'editor:fold-all': @foldAll
|
||
'editor:unfold-all': @unfoldAll
|
||
'editor:fold-current-row': @foldCurrentRow
|
||
'editor:unfold-current-row': @unfoldCurrentRow
|
||
'editor:fold-selection': @foldSelection
|
||
'editor:toggle-line-comments': @toggleLineCommentsInSelection
|
||
'editor:log-cursor-scope': @logCursorScope
|
||
'editor:checkout-head-revision': @checkoutHead
|
||
'editor:copy-path': @copyPathToPasteboard
|
||
'editor:move-line-up': @moveLineUp
|
||
'editor:move-line-down': @moveLineDown
|
||
'editor:duplicate-line': @duplicateLine
|
||
'editor:join-line': @joinLine
|
||
'editor:toggle-indent-guide': => config.set('editor.showIndentGuide', !config.get('editor.showIndentGuide'))
|
||
'editor:save-debug-snapshot': @saveDebugSnapshot
|
||
'editor:toggle-line-numbers': => config.set('editor.showLineNumbers', !config.get('editor.showLineNumbers'))
|
||
'editor:scroll-to-cursor': @scrollToCursorPosition
|
||
|
||
documentation = {}
|
||
for name, method of editorBindings
|
||
do (name, method) =>
|
||
@command name, (e) => method.call(this, e); false
|
||
|
||
# {Delegates to: EditSession.getCursor}
|
||
getCursor: -> @activeEditSession.getCursor()
|
||
|
||
# {Delegates to: EditSession.getCursors}
|
||
getCursors: -> @activeEditSession.getCursors()
|
||
|
||
# {Delegates to: EditSession.addCursorAtScreenPosition}
|
||
addCursorAtScreenPosition: (screenPosition) -> @activeEditSession.addCursorAtScreenPosition(screenPosition)
|
||
|
||
# {Delegates to: EditSession.addCursorAtBufferPosition}
|
||
addCursorAtBufferPosition: (bufferPosition) -> @activeEditSession.addCursorAtBufferPosition(bufferPosition)
|
||
|
||
# {Delegates to: EditSession.moveCursorUp}
|
||
moveCursorUp: -> @activeEditSession.moveCursorUp()
|
||
|
||
# {Delegates to: EditSession.moveCursorDown}
|
||
moveCursorDown: -> @activeEditSession.moveCursorDown()
|
||
|
||
# {Delegates to: EditSession.moveCursorLeft}
|
||
moveCursorLeft: -> @activeEditSession.moveCursorLeft()
|
||
|
||
# {Delegates to: EditSession.moveCursorRight}
|
||
moveCursorRight: -> @activeEditSession.moveCursorRight()
|
||
|
||
# {Delegates to: EditSession.moveCursorToBeginningOfWord}
|
||
moveCursorToBeginningOfWord: -> @activeEditSession.moveCursorToBeginningOfWord()
|
||
|
||
# {Delegates to: EditSession.moveCursorToEndOfWord}
|
||
moveCursorToEndOfWord: -> @activeEditSession.moveCursorToEndOfWord()
|
||
|
||
# {Delegates to: EditSession.moveCursorToBeginningOfNextWord}
|
||
moveCursorToBeginningOfNextWord: -> @activeEditSession.moveCursorToBeginningOfNextWord()
|
||
|
||
# {Delegates to: EditSession.moveCursorToTop}
|
||
moveCursorToTop: -> @activeEditSession.moveCursorToTop()
|
||
|
||
# {Delegates to: EditSession.moveCursorToBottom}
|
||
moveCursorToBottom: -> @activeEditSession.moveCursorToBottom()
|
||
|
||
# {Delegates to: EditSession.moveCursorToBeginningOfLine}
|
||
moveCursorToBeginningOfLine: -> @activeEditSession.moveCursorToBeginningOfLine()
|
||
|
||
# {Delegates to: EditSession.moveCursorToFirstCharacterOfLine}
|
||
moveCursorToFirstCharacterOfLine: -> @activeEditSession.moveCursorToFirstCharacterOfLine()
|
||
|
||
# {Delegates to: EditSession.moveCursorToEndOfLine}
|
||
moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine()
|
||
|
||
# {Delegates to: EditSession.moveLineUp}
|
||
moveLineUp: -> @activeEditSession.moveLineUp()
|
||
|
||
# {Delegates to: EditSession.moveLineDown}
|
||
moveLineDown: -> @activeEditSession.moveLineDown()
|
||
|
||
# {Delegates to: EditSession.setCursorScreenPosition}
|
||
setCursorScreenPosition: (position, options) -> @activeEditSession.setCursorScreenPosition(position, options)
|
||
|
||
# {Delegates to: EditSession.duplicateLine}
|
||
duplicateLine: -> @activeEditSession.duplicateLine()
|
||
|
||
# {Delegates to: EditSession.joinLine}
|
||
joinLine: -> @activeEditSession.joinLine()
|
||
|
||
# {Delegates to: EditSession.getCursorScreenPosition}
|
||
getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition()
|
||
|
||
# {Delegates to: EditSession.getCursorScreenRow}
|
||
getCursorScreenRow: -> @activeEditSession.getCursorScreenRow()
|
||
|
||
# {Delegates to: EditSession.setCursorBufferPosition}
|
||
setCursorBufferPosition: (position, options) -> @activeEditSession.setCursorBufferPosition(position, options)
|
||
|
||
# {Delegates to: EditSession.getCursorBufferPosition}
|
||
getCursorBufferPosition: -> @activeEditSession.getCursorBufferPosition()
|
||
|
||
# {Delegates to: EditSession.getCurrentParagraphBufferRange}
|
||
getCurrentParagraphBufferRange: -> @activeEditSession.getCurrentParagraphBufferRange()
|
||
|
||
# {Delegates to: EditSession.getWordUnderCursor}
|
||
getWordUnderCursor: (options) -> @activeEditSession.getWordUnderCursor(options)
|
||
|
||
# {Delegates to: EditSession.getSelection}
|
||
getSelection: (index) -> @activeEditSession.getSelection(index)
|
||
|
||
# {Delegates to: EditSession.getSelections}
|
||
getSelections: -> @activeEditSession.getSelections()
|
||
|
||
# {Delegates to: EditSession.getSelectionsOrderedByBufferPosition}
|
||
getSelectionsOrderedByBufferPosition: -> @activeEditSession.getSelectionsOrderedByBufferPosition()
|
||
|
||
# {Delegates to: EditSession.getLastSelectionInBuffer}
|
||
getLastSelectionInBuffer: -> @activeEditSession.getLastSelectionInBuffer()
|
||
|
||
# {Delegates to: EditSession.getSelectedText}
|
||
getSelectedText: -> @activeEditSession.getSelectedText()
|
||
|
||
# {Delegates to: EditSession.getSelectedBufferRanges}
|
||
getSelectedBufferRanges: -> @activeEditSession.getSelectedBufferRanges()
|
||
|
||
# {Delegates to: EditSession.getSelectedBufferRange}
|
||
getSelectedBufferRange: -> @activeEditSession.getSelectedBufferRange()
|
||
|
||
# {Delegates to: EditSession.setSelectedBufferRange}
|
||
setSelectedBufferRange: (bufferRange, options) -> @activeEditSession.setSelectedBufferRange(bufferRange, options)
|
||
|
||
# {Delegates to: EditSession.setSelectedBufferRanges}
|
||
setSelectedBufferRanges: (bufferRanges, options) -> @activeEditSession.setSelectedBufferRanges(bufferRanges, options)
|
||
|
||
# {Delegates to: EditSession.addSelectionForBufferRange}
|
||
addSelectionForBufferRange: (bufferRange, options) -> @activeEditSession.addSelectionForBufferRange(bufferRange, options)
|
||
|
||
# {Delegates to: EditSession.selectRight}
|
||
selectRight: -> @activeEditSession.selectRight()
|
||
|
||
# {Delegates to: EditSession.selectLeft}
|
||
selectLeft: -> @activeEditSession.selectLeft()
|
||
|
||
# {Delegates to: EditSession.selectUp}
|
||
selectUp: -> @activeEditSession.selectUp()
|
||
|
||
# {Delegates to: EditSession.selectDown}
|
||
selectDown: -> @activeEditSession.selectDown()
|
||
|
||
# {Delegates to: EditSession.selectToTop}
|
||
selectToTop: -> @activeEditSession.selectToTop()
|
||
|
||
# {Delegates to: EditSession.selectToBottom}
|
||
selectToBottom: -> @activeEditSession.selectToBottom()
|
||
|
||
# {Delegates to: EditSession.selectAll}
|
||
selectAll: -> @activeEditSession.selectAll()
|
||
|
||
# {Delegates to: EditSession.selectToBeginningOfLine}
|
||
selectToBeginningOfLine: -> @activeEditSession.selectToBeginningOfLine()
|
||
|
||
# {Delegates to: EditSession.selectToEndOfLine}
|
||
selectToEndOfLine: -> @activeEditSession.selectToEndOfLine()
|
||
|
||
# {Delegates to: EditSession.addSelectionBelow}
|
||
addSelectionBelow: -> @activeEditSession.addSelectionBelow()
|
||
|
||
# {Delegates to: EditSession.addSelectionAbove}
|
||
addSelectionAbove: -> @activeEditSession.addSelectionAbove()
|
||
|
||
# {Delegates to: EditSession.selectToBeginningOfWord}
|
||
selectToBeginningOfWord: -> @activeEditSession.selectToBeginningOfWord()
|
||
|
||
# {Delegates to: EditSession.selectToEndOfWord}
|
||
selectToEndOfWord: -> @activeEditSession.selectToEndOfWord()
|
||
|
||
# {Delegates to: EditSession.selectToBeginningOfNextWord}
|
||
selectToBeginningOfNextWord: -> @activeEditSession.selectToBeginningOfNextWord()
|
||
|
||
# {Delegates to: EditSession.selectWord}
|
||
selectWord: -> @activeEditSession.selectWord()
|
||
|
||
# {Delegates to: EditSession.selectLine}
|
||
selectLine: -> @activeEditSession.selectLine()
|
||
|
||
# {Delegates to: EditSession.selectToScreenPosition}
|
||
selectToScreenPosition: (position) -> @activeEditSession.selectToScreenPosition(position)
|
||
|
||
# {Delegates to: EditSession.transpose}
|
||
transpose: -> @activeEditSession.transpose()
|
||
|
||
# {Delegates to: EditSession.upperCase}
|
||
upperCase: -> @activeEditSession.upperCase()
|
||
|
||
# {Delegates to: EditSession.lowerCase}
|
||
lowerCase: -> @activeEditSession.lowerCase()
|
||
|
||
# {Delegates to: EditSession.clearSelections}
|
||
clearSelections: -> @activeEditSession.clearSelections()
|
||
|
||
# {Delegates to: EditSession.backspace}
|
||
backspace: -> @activeEditSession.backspace()
|
||
|
||
# {Delegates to: EditSession.backspaceToBeginningOfWord}
|
||
backspaceToBeginningOfWord: -> @activeEditSession.backspaceToBeginningOfWord()
|
||
|
||
# {Delegates to: EditSession.backspaceToBeginningOfLine}
|
||
backspaceToBeginningOfLine: -> @activeEditSession.backspaceToBeginningOfLine()
|
||
|
||
# {Delegates to: EditSession.delete}
|
||
delete: -> @activeEditSession.delete()
|
||
|
||
# {Delegates to: EditSession.deleteToEndOfWord}
|
||
deleteToEndOfWord: -> @activeEditSession.deleteToEndOfWord()
|
||
|
||
# {Delegates to: EditSession.deleteLine}
|
||
deleteLine: -> @activeEditSession.deleteLine()
|
||
|
||
# {Delegates to: EditSession.cutToEndOfLine}
|
||
cutToEndOfLine: -> @activeEditSession.cutToEndOfLine()
|
||
|
||
# {Delegates to: EditSession.insertText}
|
||
insertText: (text, options) -> @activeEditSession.insertText(text, options)
|
||
|
||
# {Delegates to: EditSession.insertNewline}
|
||
insertNewline: -> @activeEditSession.insertNewline()
|
||
|
||
# {Delegates to: EditSession.insertNewlineBelow}
|
||
insertNewlineBelow: -> @activeEditSession.insertNewlineBelow()
|
||
|
||
# {Delegates to: EditSession.insertNewlineAbove}
|
||
insertNewlineAbove: -> @activeEditSession.insertNewlineAbove()
|
||
|
||
# {Delegates to: EditSession.indent}
|
||
indent: (options) -> @activeEditSession.indent(options)
|
||
|
||
# {Delegates to: EditSession.autoIndentSelectedRows}
|
||
autoIndent: (options) -> @activeEditSession.autoIndentSelectedRows()
|
||
|
||
# {Delegates to: EditSession.indentSelectedRows}
|
||
indentSelectedRows: -> @activeEditSession.indentSelectedRows()
|
||
|
||
# {Delegates to: EditSession.outdentSelectedRows}
|
||
outdentSelectedRows: -> @activeEditSession.outdentSelectedRows()
|
||
|
||
# {Delegates to: EditSession.cutSelectedText}
|
||
cutSelection: -> @activeEditSession.cutSelectedText()
|
||
|
||
# {Delegates to: EditSession.copySelectedText}
|
||
copySelection: -> @activeEditSession.copySelectedText()
|
||
|
||
# {Delegates to: EditSession.pasteText}
|
||
paste: (options) -> @activeEditSession.pasteText(options)
|
||
|
||
# {Delegates to: EditSession.undo}
|
||
undo: -> @activeEditSession.undo()
|
||
|
||
# {Delegates to: EditSession.redo}
|
||
redo: -> @activeEditSession.redo()
|
||
|
||
# {Delegates to: EditSession.createFold}
|
||
createFold: (startRow, endRow) -> @activeEditSession.createFold(startRow, endRow)
|
||
|
||
# {Delegates to: EditSession.foldCurrentRow}
|
||
foldCurrentRow: -> @activeEditSession.foldCurrentRow()
|
||
|
||
# {Delegates to: EditSession.unfoldCurrentRow}
|
||
unfoldCurrentRow: -> @activeEditSession.unfoldCurrentRow()
|
||
|
||
# {Delegates to: EditSession.foldAll}
|
||
foldAll: -> @activeEditSession.foldAll()
|
||
|
||
# {Delegates to: EditSession.unfoldAll}
|
||
unfoldAll: -> @activeEditSession.unfoldAll()
|
||
|
||
# {Delegates to: EditSession.foldSelection}
|
||
foldSelection: -> @activeEditSession.foldSelection()
|
||
|
||
# {Delegates to: EditSession.destroyFold}
|
||
destroyFold: (foldId) -> @activeEditSession.destroyFold(foldId)
|
||
|
||
# {Delegates to: EditSession.destroyFoldsContainingBufferRow}
|
||
destroyFoldsContainingBufferRow: (bufferRow) -> @activeEditSession.destroyFoldsContainingBufferRow(bufferRow)
|
||
|
||
# {Delegates to: EditSession.isFoldedAtScreenRow}
|
||
isFoldedAtScreenRow: (screenRow) -> @activeEditSession.isFoldedAtScreenRow(screenRow)
|
||
|
||
# {Delegates to: EditSession.isFoldedAtBufferRow}
|
||
isFoldedAtBufferRow: (bufferRow) -> @activeEditSession.isFoldedAtBufferRow(bufferRow)
|
||
|
||
# {Delegates to: EditSession.isFoldedAtCursorRow}
|
||
isFoldedAtCursorRow: -> @activeEditSession.isFoldedAtCursorRow()
|
||
|
||
# {Delegates to: EditSession.lineForScreenRow}
|
||
lineForScreenRow: (screenRow) -> @activeEditSession.lineForScreenRow(screenRow)
|
||
|
||
# {Delegates to: EditSession.linesForScreenRows}
|
||
linesForScreenRows: (start, end) -> @activeEditSession.linesForScreenRows(start, end)
|
||
|
||
# {Delegates to: EditSession.getScreenLineCount}
|
||
getScreenLineCount: -> @activeEditSession.getScreenLineCount()
|
||
|
||
# {Delegates to: EditSession.setSoftWrapColumn}
|
||
setSoftWrapColumn: (softWrapColumn) ->
|
||
softWrapColumn ?= @calcSoftWrapColumn()
|
||
@activeEditSession.setSoftWrapColumn(softWrapColumn) if softWrapColumn
|
||
|
||
# {Delegates to: EditSession.maxScreenLineLength}
|
||
maxScreenLineLength: -> @activeEditSession.maxScreenLineLength()
|
||
|
||
# {Delegates to: EditSession.getLastScreenRow}
|
||
getLastScreenRow: -> @activeEditSession.getLastScreenRow()
|
||
|
||
# {Delegates to: EditSession.clipScreenPosition}
|
||
clipScreenPosition: (screenPosition, options={}) -> @activeEditSession.clipScreenPosition(screenPosition, options)
|
||
|
||
# {Delegates to: EditSession.screenPositionForBufferPosition}
|
||
screenPositionForBufferPosition: (position, options) -> @activeEditSession.screenPositionForBufferPosition(position, options)
|
||
|
||
# {Delegates to: EditSession.bufferPositionForScreenPosition}
|
||
bufferPositionForScreenPosition: (position, options) -> @activeEditSession.bufferPositionForScreenPosition(position, options)
|
||
|
||
# {Delegates to: EditSession.screenRangeForBufferRange}
|
||
screenRangeForBufferRange: (range) -> @activeEditSession.screenRangeForBufferRange(range)
|
||
|
||
# {Delegates to: EditSession.bufferRangeForScreenRange}
|
||
bufferRangeForScreenRange: (range) -> @activeEditSession.bufferRangeForScreenRange(range)
|
||
|
||
# {Delegates to: EditSession.bufferRowsForScreenRows}
|
||
bufferRowsForScreenRows: (startRow, endRow) -> @activeEditSession.bufferRowsForScreenRows(startRow, endRow)
|
||
|
||
# {Delegates to: EditSession.getLastScreenRow}
|
||
getLastScreenRow: -> @activeEditSession.getLastScreenRow()
|
||
|
||
# Public: Emulates the "page down" key, where the last row of a buffer scrolls to become the first.
|
||
pageDown: ->
|
||
newScrollTop = @scrollTop() + @scrollView[0].clientHeight
|
||
@activeEditSession.moveCursorDown(@getPageRows())
|
||
@scrollTop(newScrollTop, adjustVerticalScrollbar: true)
|
||
|
||
# Public: Emulates the "page up" key, where the frst row of a buffer scrolls to become the last.
|
||
pageUp: ->
|
||
newScrollTop = @scrollTop() - @scrollView[0].clientHeight
|
||
@activeEditSession.moveCursorUp(@getPageRows())
|
||
@scrollTop(newScrollTop, adjustVerticalScrollbar: true)
|
||
|
||
# Gets the number of actual page rows existing in an editor.
|
||
#
|
||
# Returns a {Number}.
|
||
getPageRows: ->
|
||
Math.max(1, Math.ceil(@scrollView[0].clientHeight / @lineHeight))
|
||
|
||
# Set whether invisible characters are shown.
|
||
#
|
||
# showInvisibles - A {Boolean} which, if `true`, show invisible characters
|
||
setShowInvisibles: (showInvisibles) ->
|
||
return if showInvisibles == @showInvisibles
|
||
@showInvisibles = showInvisibles
|
||
@resetDisplay()
|
||
|
||
# Defines which characters are invisible.
|
||
#
|
||
# invisibles - A hash defining the invisible characters: The defaults are:
|
||
# eol: `\u00ac`
|
||
# space: `\u00b7`
|
||
# tab: `\u00bb`
|
||
# cr: `\u00a4`
|
||
setInvisibles: (@invisibles={}) ->
|
||
_.defaults @invisibles,
|
||
eol: '\u00ac'
|
||
space: '\u00b7'
|
||
tab: '\u00bb'
|
||
cr: '\u00a4'
|
||
@resetDisplay()
|
||
|
||
# Sets whether you want to show the indentation guides.
|
||
#
|
||
# showIndentGuide - A {Boolean} you can set to `true` if you want to see the indentation guides.
|
||
setShowIndentGuide: (showIndentGuide) ->
|
||
return if showIndentGuide == @showIndentGuide
|
||
@showIndentGuide = showIndentGuide
|
||
@resetDisplay()
|
||
|
||
# {Delegates to: Buffer.checkoutHead}
|
||
checkoutHead: -> @getBuffer().checkoutHead()
|
||
|
||
# {Delegates to: EditSession.setText}
|
||
setText: (text) -> @activeEditSession.setText(text)
|
||
|
||
# {Delegates to: EditSession.getText}
|
||
getText: -> @activeEditSession.getText()
|
||
|
||
# {Delegates to: EditSession.getPath}
|
||
getPath: -> @activeEditSession?.getPath()
|
||
|
||
# {Delegates to: Buffer.getLineCount}
|
||
getLineCount: -> @getBuffer().getLineCount()
|
||
|
||
# {Delegates to: Buffer.getLastRow}
|
||
getLastBufferRow: -> @getBuffer().getLastRow()
|
||
|
||
# {Delegates to: Buffer.getTextInRange}
|
||
getTextInRange: (range) -> @getBuffer().getTextInRange(range)
|
||
|
||
# {Delegates to: Buffer.getEofPosition}
|
||
getEofPosition: -> @getBuffer().getEofPosition()
|
||
|
||
# {Delegates to: Buffer.lineForRow}
|
||
lineForBufferRow: (row) -> @getBuffer().lineForRow(row)
|
||
|
||
# {Delegates to: Buffer.lineLengthForRow}
|
||
lineLengthForBufferRow: (row) -> @getBuffer().lineLengthForRow(row)
|
||
|
||
# {Delegates to: Buffer.rangeForRow}
|
||
rangeForBufferRow: (row) -> @getBuffer().rangeForRow(row)
|
||
|
||
# {Delegates to: Buffer.scanInRange}
|
||
scanInBufferRange: (args...) -> @getBuffer().scanInRange(args...)
|
||
|
||
# {Delegates to: Buffer.backwardsScanInRange}
|
||
backwardsScanInBufferRange: (args...) -> @getBuffer().backwardsScanInRange(args...)
|
||
|
||
### Internal ###
|
||
|
||
configure: ->
|
||
@observeConfig 'editor.showLineNumbers', (showLineNumbers) => @gutter.setShowLineNumbers(showLineNumbers)
|
||
@observeConfig 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles)
|
||
@observeConfig 'editor.showIndentGuide', (showIndentGuide) => @setShowIndentGuide(showIndentGuide)
|
||
@observeConfig 'editor.invisibles', (invisibles) => @setInvisibles(invisibles)
|
||
@observeConfig 'editor.fontSize', (fontSize) => @setFontSize(fontSize)
|
||
@observeConfig 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily)
|
||
|
||
handleEvents: ->
|
||
@on 'focus', =>
|
||
@hiddenInput.focus()
|
||
false
|
||
|
||
@hiddenInput.on 'focus', =>
|
||
@isFocused = true
|
||
@addClass 'is-focused'
|
||
|
||
@hiddenInput.on 'focusout', =>
|
||
@isFocused = false
|
||
@removeClass 'is-focused'
|
||
|
||
@underlayer.on 'mousedown', (e) =>
|
||
@renderedLines.trigger(e)
|
||
false if @isFocused
|
||
|
||
@overlayer.on 'mousedown', (e) =>
|
||
@overlayer.hide()
|
||
clickedElement = document.elementFromPoint(e.pageX, e.pageY)
|
||
@overlayer.show()
|
||
e.target = clickedElement
|
||
$(clickedElement).trigger(e)
|
||
false if @isFocused
|
||
|
||
@renderedLines.on 'mousedown', '.fold.line', (e) =>
|
||
@destroyFold($(e.currentTarget).attr('fold-id'))
|
||
false
|
||
|
||
@renderedLines.on 'mousedown', (e) =>
|
||
clickCount = e.originalEvent.detail
|
||
|
||
screenPosition = @screenPositionFromMouseEvent(e)
|
||
if clickCount == 1
|
||
if e.metaKey
|
||
@addCursorAtScreenPosition(screenPosition)
|
||
else if e.shiftKey
|
||
@selectToScreenPosition(screenPosition)
|
||
else
|
||
@setCursorScreenPosition(screenPosition)
|
||
else if clickCount == 2
|
||
@activeEditSession.selectWord() unless e.shiftKey
|
||
else if clickCount == 3
|
||
@activeEditSession.selectLine() unless e.shiftKey
|
||
|
||
@selectOnMousemoveUntilMouseup() unless e.ctrlKey or e.originalEvent.which > 1
|
||
|
||
@on "textInput", (e) =>
|
||
@insertText(e.originalEvent.data)
|
||
false
|
||
|
||
@scrollView.on 'mousewheel', (e) =>
|
||
if delta = e.originalEvent.wheelDeltaY
|
||
@scrollTop(@scrollTop() - delta)
|
||
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')
|
||
|
||
selectOnMousemoveUntilMouseup: ->
|
||
lastMoveEvent = null
|
||
moveHandler = (event = lastMoveEvent) =>
|
||
if event
|
||
@selectToScreenPosition(@screenPositionFromMouseEvent(event))
|
||
lastMoveEvent = event
|
||
|
||
$(document).on "mousemove.editor-#{@id}", moveHandler
|
||
interval = setInterval(moveHandler, 20)
|
||
|
||
$(document).one "mouseup.editor-#{@id}", =>
|
||
clearInterval(interval)
|
||
$(document).off 'mousemove', moveHandler
|
||
reverse = @activeEditSession.getLastSelection().isReversed()
|
||
@activeEditSession.mergeIntersectingSelections({reverse})
|
||
@activeEditSession.finalizeSelections()
|
||
@syncCursorAnimations()
|
||
|
||
afterAttach: (onDom) ->
|
||
return unless onDom
|
||
@redraw() if @redrawOnReattach
|
||
return if @attached
|
||
@attached = true
|
||
@calculateDimensions()
|
||
@setSoftWrapColumn() if @activeEditSession.getSoftWrap()
|
||
@subscribe $(window), "resize.editor-#{@id}", => @requestDisplayUpdate()
|
||
@focus() if @isFocused
|
||
|
||
if pane = @getPane()
|
||
@active = @is(pane.activeView)
|
||
@subscribe pane, 'pane:active-item-changed', (event, item) =>
|
||
wasActive = @active
|
||
@active = @is(pane.activeView)
|
||
@redraw() if @active and not wasActive
|
||
|
||
@resetDisplay()
|
||
|
||
@trigger 'editor:attached', [this]
|
||
|
||
edit: (editSession) ->
|
||
return if editSession is @activeEditSession
|
||
|
||
if @activeEditSession
|
||
@saveScrollPositionForActiveEditSession()
|
||
@activeEditSession.off(".editor")
|
||
|
||
@activeEditSession = editSession
|
||
|
||
return unless @activeEditSession?
|
||
|
||
@activeEditSession.setVisible(true)
|
||
|
||
@activeEditSession.on "contents-conflicted.editor", =>
|
||
@showBufferConflictAlert(@activeEditSession)
|
||
|
||
@activeEditSession.on "path-changed.editor", =>
|
||
@reloadGrammar()
|
||
@trigger 'editor:path-changed'
|
||
|
||
@activeEditSession.on "grammar-changed.editor", =>
|
||
@trigger 'editor:grammar-changed'
|
||
|
||
@activeEditSession.on 'selection-added.editor', (selection) =>
|
||
@newCursors.push(selection.cursor)
|
||
@newSelections.push(selection)
|
||
@requestDisplayUpdate()
|
||
|
||
@activeEditSession.on 'screen-lines-changed.editor', (e) =>
|
||
@handleScreenLinesChange(e)
|
||
|
||
@trigger 'editor:path-changed'
|
||
@resetDisplay()
|
||
|
||
if @attached and @activeEditSession.buffer.isInConflict()
|
||
_.defer => @showBufferConflictAlert(@activeEditSession) # Display after editSession has a chance to display
|
||
|
||
getModel: ->
|
||
@activeEditSession
|
||
|
||
setModel: (editSession) ->
|
||
@edit(editSession)
|
||
|
||
showBufferConflictAlert: (editSession) ->
|
||
atom.confirm(
|
||
editSession.getPath(),
|
||
"Has changed on disk. Do you want to reload it?",
|
||
"Reload", (=> editSession.buffer.reload()),
|
||
"Cancel"
|
||
)
|
||
|
||
scrollTop: (scrollTop, options={}) ->
|
||
return @cachedScrollTop or 0 unless scrollTop?
|
||
maxScrollTop = @verticalScrollbar.prop('scrollHeight') - @verticalScrollbar.height()
|
||
scrollTop = Math.floor(Math.max(0, Math.min(maxScrollTop, scrollTop)))
|
||
return if scrollTop == @cachedScrollTop
|
||
@cachedScrollTop = scrollTop
|
||
|
||
@updateDisplay() if @attached
|
||
|
||
@renderedLines.css('top', -scrollTop)
|
||
@underlayer.css('top', -scrollTop)
|
||
@overlayer.css('top', -scrollTop)
|
||
@gutter.lineNumbers.css('top', -scrollTop)
|
||
if options?.adjustVerticalScrollbar ? true
|
||
@verticalScrollbar.scrollTop(scrollTop)
|
||
|
||
scrollBottom: (scrollBottom) ->
|
||
if scrollBottom?
|
||
@scrollTop(scrollBottom - @scrollView.height())
|
||
else
|
||
@scrollTop() + @scrollView.height()
|
||
|
||
### Public ###
|
||
|
||
# Retrieves the {EditSession}'s buffer.
|
||
#
|
||
# Returns the current {Buffer}.
|
||
getBuffer: -> @activeEditSession.buffer
|
||
|
||
# Scrolls the editor to the bottom.
|
||
scrollToBottom: ->
|
||
@scrollBottom(@getScreenLineCount() * @lineHeight)
|
||
|
||
# Scrolls the editor to the position of the most recently added cursor.
|
||
#
|
||
# The editor is also centered.
|
||
scrollToCursorPosition: ->
|
||
@scrollToBufferPosition(@getCursorBufferPosition(), center: true)
|
||
|
||
# Scrolls the editor to the given buffer position.
|
||
#
|
||
# bufferPosition - An object that represents a buffer position. It can be either
|
||
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
||
# options - A hash matching the options available to {.scrollToPixelPosition}
|
||
scrollToBufferPosition: (bufferPosition, options) ->
|
||
@scrollToPixelPosition(@pixelPositionForBufferPosition(bufferPosition), options)
|
||
|
||
# Scrolls the editor to the given screen position.
|
||
#
|
||
# screenPosition - An object that represents a buffer position. It can be either
|
||
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
||
# options - A hash matching the options available to {.scrollToPixelPosition}
|
||
scrollToScreenPosition: (screenPosition, options) ->
|
||
@scrollToPixelPosition(@pixelPositionForScreenPosition(screenPosition), options)
|
||
|
||
# Scrolls the editor to the given pixel position.
|
||
#
|
||
# pixelPosition - An object that represents a pixel position. It can be either
|
||
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
||
# options - A hash with the following keys:
|
||
# center: if `true`, the position is scrolled such that it's in the center of the editor
|
||
scrollToPixelPosition: (pixelPosition, options) ->
|
||
return unless @attached
|
||
@scrollVertically(pixelPosition, options)
|
||
@scrollHorizontally(pixelPosition)
|
||
|
||
# Given a buffer range, this highlights all the folds within that range
|
||
#
|
||
# "Highlighting" essentially just adds the `selected` class to the line
|
||
#
|
||
# bufferRange - The {Range} to check
|
||
highlightFoldsContainingBufferRange: (bufferRange) ->
|
||
screenLines = @linesForScreenRows(@firstRenderedScreenRow, @lastRenderedScreenRow)
|
||
for screenLine, i in screenLines
|
||
if fold = screenLine.fold
|
||
screenRow = @firstRenderedScreenRow + i
|
||
element = @lineElementForScreenRow(screenRow)
|
||
|
||
if bufferRange.intersectsWith(fold.getBufferRange())
|
||
element.addClass('selected')
|
||
else
|
||
element.removeClass('selected')
|
||
|
||
setScrollPositionFromActiveEditSession: ->
|
||
@scrollTop(@activeEditSession.scrollTop ? 0)
|
||
@scrollView.scrollLeft(@activeEditSession.scrollLeft ? 0)
|
||
|
||
saveScrollPositionForActiveEditSession: ->
|
||
@activeEditSession.setScrollTop(@scrollTop())
|
||
@activeEditSession.setScrollLeft(@scrollView.scrollLeft())
|
||
|
||
# {Delegates to: EditSession.setSoftTabs}
|
||
toggleSoftTabs: ->
|
||
@activeEditSession.setSoftTabs(not @activeEditSession.softTabs)
|
||
|
||
# Activates soft wraps in the editor.
|
||
toggleSoftWrap: ->
|
||
@setSoftWrap(not @activeEditSession.getSoftWrap())
|
||
|
||
calcSoftWrapColumn: ->
|
||
if @activeEditSession.getSoftWrap()
|
||
Math.floor(@scrollView.width() / @charWidth)
|
||
else
|
||
Infinity
|
||
|
||
# Sets the soft wrap column for the editor.
|
||
#
|
||
# softWrap - A {Boolean} which, if `true`, sets soft wraps
|
||
# softWrapColumn - A {Number} indicating the length of a line in the editor when soft
|
||
# wrapping turns on
|
||
setSoftWrap: (softWrap, softWrapColumn=undefined) ->
|
||
@activeEditSession.setSoftWrap(softWrap)
|
||
@setSoftWrapColumn(softWrapColumn) if @attached
|
||
if @activeEditSession.getSoftWrap()
|
||
@addClass 'soft-wrap'
|
||
@scrollView.scrollLeft(0)
|
||
@_setSoftWrapColumn = => @setSoftWrapColumn()
|
||
$(window).on "resize.editor-#{@id}", @_setSoftWrapColumn
|
||
else
|
||
@removeClass 'soft-wrap'
|
||
$(window).off 'resize', @_setSoftWrapColumn
|
||
|
||
# Sets the font size for the editor.
|
||
#
|
||
# fontSize - A {Number} indicating the font size in pixels.
|
||
setFontSize: (fontSize) ->
|
||
headTag = $("head")
|
||
styleTag = headTag.find("style.font-size")
|
||
if styleTag.length == 0
|
||
styleTag = $$ -> @style class: 'font-size'
|
||
headTag.append styleTag
|
||
|
||
styleTag.text(".editor {font-size: #{fontSize}px}")
|
||
|
||
if @isOnDom()
|
||
@redraw()
|
||
else
|
||
@redrawOnReattach = @attached
|
||
|
||
# Retrieves the font size for the editor.
|
||
#
|
||
# Returns a {Number} indicating the font size in pixels.
|
||
getFontSize: ->
|
||
parseInt(@css("font-size"))
|
||
|
||
# Sets the font family for the editor.
|
||
#
|
||
# fontFamily - A {String} identifying the CSS `font-family`,
|
||
setFontFamily: (fontFamily) ->
|
||
headTag = $("head")
|
||
styleTag = headTag.find("style.editor-font-family")
|
||
|
||
if fontFamily?
|
||
if styleTag.length == 0
|
||
styleTag = $$ -> @style class: 'editor-font-family'
|
||
headTag.append styleTag
|
||
styleTag.text(".editor {font-family: #{fontFamily}}")
|
||
else
|
||
styleTag.remove()
|
||
|
||
@redraw()
|
||
|
||
# Gets the font family for the editor.
|
||
#
|
||
# Returns a {String} identifying the CSS `font-family`,
|
||
getFontFamily: -> @css("font-family")
|
||
|
||
# Clears the CSS `font-family` property from the editor.
|
||
clearFontFamily: ->
|
||
$('head style.editor-font-family').remove()
|
||
|
||
# Clears the CSS `font-family` property from the editor.
|
||
redraw: ->
|
||
return unless @hasParent()
|
||
return unless @attached
|
||
@redrawOnReattach = false
|
||
@calculateDimensions()
|
||
@updatePaddingOfRenderedLines()
|
||
@updateLayerDimensions()
|
||
@requestDisplayUpdate()
|
||
|
||
splitLeft: (items...) ->
|
||
@getPane()?.splitLeft(items...).activeView
|
||
|
||
splitRight: (items...) ->
|
||
@getPane()?.splitRight(items...).activeView
|
||
|
||
splitUp: (items...) ->
|
||
@getPane()?.splitUp(items...).activeView
|
||
|
||
splitDown: (items...) ->
|
||
@getPane()?.splitDown(items...).activeView
|
||
|
||
# Retrieve's the `Editor`'s pane.
|
||
#
|
||
# Returns a {Pane}.
|
||
getPane: ->
|
||
@parent('.item-views').parent('.pane').view()
|
||
|
||
remove: (selector, keepData) ->
|
||
return super if keepData or @removed
|
||
super
|
||
rootView?.focus()
|
||
|
||
beforeRemove: ->
|
||
@trigger 'editor:will-be-removed'
|
||
@removed = true
|
||
@activeEditSession?.destroy()
|
||
$(window).off(".editor-#{@id}")
|
||
$(document).off(".editor-#{@id}")
|
||
|
||
getCursorView: (index) ->
|
||
index ?= @cursorViews.length - 1
|
||
@cursorViews[index]
|
||
|
||
getCursorViews: ->
|
||
new Array(@cursorViews...)
|
||
|
||
addCursorView: (cursor, options) ->
|
||
cursorView = new CursorView(cursor, this, options)
|
||
@cursorViews.push(cursorView)
|
||
@overlayer.append(cursorView)
|
||
cursorView
|
||
|
||
removeCursorView: (cursorView) ->
|
||
_.remove(@cursorViews, cursorView)
|
||
|
||
getSelectionView: (index) ->
|
||
index ?= @selectionViews.length - 1
|
||
@selectionViews[index]
|
||
|
||
getSelectionViews: ->
|
||
new Array(@selectionViews...)
|
||
|
||
addSelectionView: (selection) ->
|
||
selectionView = new SelectionView({editor: this, selection})
|
||
@selectionViews.push(selectionView)
|
||
@underlayer.append(selectionView)
|
||
selectionView
|
||
|
||
removeSelectionView: (selectionView) ->
|
||
_.remove(@selectionViews, selectionView)
|
||
|
||
removeAllCursorAndSelectionViews: ->
|
||
cursorView.remove() for cursorView in @getCursorViews()
|
||
selectionView.remove() for selectionView in @getSelectionViews()
|
||
|
||
appendToLinesView: (view) ->
|
||
@overlayer.append(view)
|
||
|
||
### Internal ###
|
||
|
||
|
||
# Scrolls the editor vertically to a given position.
|
||
scrollVertically: (pixelPosition, {center}={}) ->
|
||
scrollViewHeight = @scrollView.height()
|
||
scrollTop = @scrollTop()
|
||
scrollBottom = scrollTop + scrollViewHeight
|
||
|
||
if center
|
||
unless scrollTop < pixelPosition.top < scrollBottom
|
||
@scrollTop(pixelPosition.top - (scrollViewHeight / 2))
|
||
else
|
||
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
|
||
if desiredBottom > scrollBottom
|
||
@scrollTop(desiredBottom - scrollViewHeight)
|
||
else if desiredTop < scrollTop
|
||
@scrollTop(desiredTop)
|
||
|
||
# Scrolls the editor horizontally to a given position.
|
||
scrollHorizontally: (pixelPosition) ->
|
||
return if @activeEditSession.getSoftWrap()
|
||
|
||
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)
|
||
|
||
calculateDimensions: ->
|
||
fragment = $('<div class="line" style="position: absolute; visibility: hidden;"><span>x</span></div>')
|
||
@renderedLines.append(fragment)
|
||
|
||
lineRect = fragment[0].getBoundingClientRect()
|
||
charRect = fragment.find('span')[0].getBoundingClientRect()
|
||
@lineHeight = lineRect.height
|
||
@charWidth = charRect.width
|
||
@charHeight = charRect.height
|
||
fragment.remove()
|
||
|
||
updateLayerDimensions: ->
|
||
height = @lineHeight * @getScreenLineCount()
|
||
unless @layerHeight == height
|
||
@layerHeight = height
|
||
@underlayer.height(@layerHeight)
|
||
@renderedLines.height(@layerHeight)
|
||
@overlayer.height(@layerHeight)
|
||
@verticalScrollbarContent.height(@layerHeight)
|
||
@scrollBottom(height) if @scrollBottom() > height
|
||
|
||
minWidth = @charWidth * @maxScreenLineLength() + 20
|
||
unless @layerMinWidth == minWidth
|
||
@renderedLines.css('min-width', minWidth)
|
||
@underlayer.css('min-width', minWidth)
|
||
@overlayer.css('min-width', minWidth)
|
||
@layerMinWidth = minWidth
|
||
@trigger 'editor:min-width-changed'
|
||
|
||
clearRenderedLines: ->
|
||
@renderedLines.empty()
|
||
@firstRenderedScreenRow = null
|
||
@lastRenderedScreenRow = null
|
||
|
||
resetDisplay: ->
|
||
return unless @attached
|
||
|
||
@clearRenderedLines()
|
||
@removeAllCursorAndSelectionViews()
|
||
@updateLayerDimensions()
|
||
@setScrollPositionFromActiveEditSession()
|
||
@newCursors = @activeEditSession.getCursors()
|
||
@newSelections = @activeEditSession.getSelections()
|
||
@updateDisplay(suppressAutoScroll: true)
|
||
|
||
requestDisplayUpdate: ->
|
||
return if @pendingDisplayUpdate
|
||
return unless @isVisible()
|
||
@pendingDisplayUpdate = true
|
||
_.nextTick =>
|
||
@updateDisplay()
|
||
@pendingDisplayUpdate = false
|
||
|
||
updateDisplay: (options={}) ->
|
||
return unless @attached and @activeEditSession
|
||
return if @activeEditSession.destroyed
|
||
unless @isVisible()
|
||
@redrawOnReattach = true
|
||
return
|
||
|
||
@updateRenderedLines()
|
||
@highlightCursorLine()
|
||
@updateCursorViews()
|
||
@updateSelectionViews()
|
||
@autoscroll(options)
|
||
@trigger 'editor:display-updated'
|
||
|
||
updateCursorViews: ->
|
||
if @newCursors.length > 0
|
||
@addCursorView(cursor) for cursor in @newCursors when not cursor.destroyed
|
||
@syncCursorAnimations()
|
||
@newCursors = []
|
||
|
||
for cursorView in @getCursorViews()
|
||
if cursorView.needsRemoval
|
||
cursorView.remove()
|
||
else if cursorView.needsUpdate
|
||
cursorView.updateDisplay()
|
||
|
||
updateSelectionViews: ->
|
||
if @newSelections.length > 0
|
||
@addSelectionView(selection) for selection in @newSelections when not selection.destroyed
|
||
@newSelections = []
|
||
|
||
for selectionView in @getSelectionViews()
|
||
if selectionView.needsRemoval
|
||
selectionView.remove()
|
||
else
|
||
selectionView.updateDisplay()
|
||
|
||
syncCursorAnimations: ->
|
||
for cursorView in @getCursorViews()
|
||
do (cursorView) -> cursorView.resetBlinking()
|
||
|
||
autoscroll: (options={}) ->
|
||
for cursorView in @getCursorViews()
|
||
if !options.suppressAutoScroll and cursorView.needsAutoscroll()
|
||
@scrollToPixelPosition(cursorView.getPixelPosition())
|
||
cursorView.clearAutoscroll()
|
||
|
||
for selectionView in @getSelectionViews()
|
||
if !options.suppressAutoScroll and selectionView.needsAutoscroll()
|
||
@scrollToPixelPosition(selectionView.getCenterPixelPosition(), center: true)
|
||
selectionView.highlight()
|
||
selectionView.clearAutoscroll()
|
||
|
||
updateRenderedLines: ->
|
||
firstVisibleScreenRow = @getFirstVisibleScreenRow()
|
||
lastVisibleScreenRow = @getLastVisibleScreenRow()
|
||
|
||
if @firstRenderedScreenRow? and firstVisibleScreenRow >= @firstRenderedScreenRow and lastVisibleScreenRow <= @lastRenderedScreenRow
|
||
renderFrom = @firstRenderedScreenRow
|
||
renderTo = Math.min(@getLastScreenRow(), @lastRenderedScreenRow)
|
||
else
|
||
renderFrom = Math.max(0, firstVisibleScreenRow - @lineOverdraw)
|
||
renderTo = Math.min(@getLastScreenRow(), lastVisibleScreenRow + @lineOverdraw)
|
||
|
||
if @pendingChanges.length == 0 and @firstRenderedScreenRow and @firstRenderedScreenRow <= renderFrom and renderTo <= @lastRenderedScreenRow
|
||
return
|
||
|
||
@gutter.updateLineNumbers(@pendingChanges, renderFrom, renderTo)
|
||
intactRanges = @computeIntactRanges()
|
||
@pendingChanges = []
|
||
@truncateIntactRanges(intactRanges, renderFrom, renderTo)
|
||
@clearDirtyRanges(intactRanges)
|
||
@fillDirtyRanges(intactRanges, renderFrom, renderTo)
|
||
@firstRenderedScreenRow = renderFrom
|
||
@lastRenderedScreenRow = renderTo
|
||
@updateLayerDimensions()
|
||
@updatePaddingOfRenderedLines()
|
||
|
||
computeIntactRanges: ->
|
||
return [] if !@firstRenderedScreenRow? and !@lastRenderedScreenRow?
|
||
|
||
intactRanges = [{start: @firstRenderedScreenRow, end: @lastRenderedScreenRow, domStart: 0}]
|
||
|
||
if not @mini and @showIndentGuide
|
||
trailingEmptyLineChanges = []
|
||
for change in @pendingChanges
|
||
continue unless change.bufferDelta?
|
||
start = change.end + change.bufferDelta + 1
|
||
continue unless @lineForBufferRow(start) is ''
|
||
end = start
|
||
end++ while @lineForBufferRow(end + 1) is ''
|
||
trailingEmptyLineChanges.push({start, end, screenDelta: 0})
|
||
@pendingChanges.push(trailingEmptyLineChanges...)
|
||
|
||
for change in @pendingChanges
|
||
newIntactRanges = []
|
||
for range in intactRanges
|
||
if change.end < range.start and change.screenDelta != 0
|
||
newIntactRanges.push(
|
||
start: range.start + change.screenDelta
|
||
end: range.end + change.screenDelta
|
||
domStart: range.domStart
|
||
)
|
||
else if change.end < range.start or change.start > range.end
|
||
newIntactRanges.push(range)
|
||
else
|
||
if change.start > range.start
|
||
newIntactRanges.push(
|
||
start: range.start
|
||
end: change.start - 1
|
||
domStart: range.domStart)
|
||
if change.end < range.end
|
||
newIntactRanges.push(
|
||
start: change.end + change.screenDelta + 1
|
||
end: range.end + change.screenDelta
|
||
domStart: range.domStart + change.end + 1 - range.start
|
||
)
|
||
intactRanges = newIntactRanges
|
||
@pendingChanges = []
|
||
intactRanges
|
||
|
||
truncateIntactRanges: (intactRanges, renderFrom, renderTo) ->
|
||
i = 0
|
||
while i < intactRanges.length
|
||
range = intactRanges[i]
|
||
if range.start < renderFrom
|
||
range.domStart += renderFrom - range.start
|
||
range.start = renderFrom
|
||
if range.end > renderTo
|
||
range.end = renderTo
|
||
if range.start >= range.end
|
||
intactRanges.splice(i--, 1)
|
||
i++
|
||
intactRanges.sort (a, b) -> a.domStart - b.domStart
|
||
|
||
clearDirtyRanges: (intactRanges) ->
|
||
renderedLines = @renderedLines[0]
|
||
killLine = (line) ->
|
||
next = line.nextSibling
|
||
renderedLines.removeChild(line)
|
||
next
|
||
|
||
if intactRanges.length == 0
|
||
@renderedLines.empty()
|
||
else if currentLine = renderedLines.firstChild
|
||
domPosition = 0
|
||
for intactRange in intactRanges
|
||
while intactRange.domStart > domPosition
|
||
currentLine = killLine(currentLine)
|
||
domPosition++
|
||
for i in [intactRange.start..intactRange.end]
|
||
currentLine = currentLine.nextSibling
|
||
domPosition++
|
||
while currentLine
|
||
currentLine = killLine(currentLine)
|
||
|
||
fillDirtyRanges: (intactRanges, renderFrom, renderTo) ->
|
||
renderedLines = @renderedLines[0]
|
||
nextIntact = intactRanges.shift()
|
||
currentLine = renderedLines.firstChild
|
||
|
||
row = renderFrom
|
||
while row <= renderTo
|
||
if row == nextIntact?.end + 1
|
||
nextIntact = intactRanges.shift()
|
||
if !nextIntact or row < nextIntact.start
|
||
if nextIntact
|
||
dirtyRangeEnd = nextIntact.start - 1
|
||
else
|
||
dirtyRangeEnd = renderTo
|
||
|
||
for lineElement in @buildLineElementsForScreenRows(row, dirtyRangeEnd)
|
||
renderedLines.insertBefore(lineElement, currentLine)
|
||
row++
|
||
else
|
||
currentLine = currentLine.nextSibling
|
||
row++
|
||
|
||
updatePaddingOfRenderedLines: ->
|
||
paddingTop = @firstRenderedScreenRow * @lineHeight
|
||
@renderedLines.css('padding-top', paddingTop)
|
||
@gutter.lineNumbers.css('padding-top', paddingTop)
|
||
|
||
paddingBottom = (@getLastScreenRow() - @lastRenderedScreenRow) * @lineHeight
|
||
@renderedLines.css('padding-bottom', paddingBottom)
|
||
@gutter.lineNumbers.css('padding-bottom', paddingBottom)
|
||
|
||
### Public ###
|
||
|
||
# Retrieves the number of the row that is visible and currently at the top of the editor.
|
||
#
|
||
# Returns a {Number}.
|
||
getFirstVisibleScreenRow: ->
|
||
Math.floor(@scrollTop() / @lineHeight)
|
||
|
||
# Retrieves the number of the row that is visible and currently at the top of the editor.
|
||
#
|
||
# Returns a {Number}.
|
||
getLastVisibleScreenRow: ->
|
||
calculatedRow = Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1
|
||
Math.max(0, Math.min(@getScreenLineCount() - 1, calculatedRow))
|
||
|
||
# Given a row number, identifies if it is currently visible.
|
||
#
|
||
# row - A row {Number} to check
|
||
#
|
||
# Returns a {Boolean}.
|
||
isScreenRowVisible: (row) ->
|
||
@getFirstVisibleScreenRow() <= row <= @getLastVisibleScreenRow()
|
||
|
||
### Internal ###
|
||
|
||
handleScreenLinesChange: (change) ->
|
||
@pendingChanges.push(change)
|
||
@requestDisplayUpdate()
|
||
|
||
buildLineElementForScreenRow: (screenRow) ->
|
||
@buildLineElementsForScreenRows(screenRow, screenRow)[0]
|
||
|
||
buildLineElementsForScreenRows: (startRow, endRow) ->
|
||
div = document.createElement('div')
|
||
div.innerHTML = @buildLinesHtml(startRow, endRow)
|
||
new Array(div.children...)
|
||
|
||
buildLinesHtml: (startRow, endRow) ->
|
||
lines = @activeEditSession.linesForScreenRows(startRow, endRow)
|
||
htmlLines = []
|
||
screenRow = startRow
|
||
for line in @activeEditSession.linesForScreenRows(startRow, endRow)
|
||
htmlLines.push(@buildLineHtml(line, screenRow++))
|
||
htmlLines.join('\n\n')
|
||
|
||
buildEndOfLineInvisibles: (screenLine) ->
|
||
invisibles = []
|
||
for invisible in @getEndOfLineInvisibles(screenLine)
|
||
invisibles.push("<span class='invisible-character'>#{invisible}</span>")
|
||
invisibles.join('')
|
||
|
||
getEndOfLineInvisibles: (screenLine) ->
|
||
return [] unless @showInvisibles and @invisibles
|
||
return [] if @mini or screenLine.isSoftWrapped()
|
||
|
||
invisibles = []
|
||
invisibles.push(@invisibles.cr) if @invisibles.cr and screenLine.lineEnding is '\r\n'
|
||
invisibles.push(@invisibles.eol) if @invisibles.eol
|
||
invisibles
|
||
|
||
buildEmptyLineHtml: (screenRow) ->
|
||
if not @mini and @showIndentGuide
|
||
guideIndentation = 0
|
||
while --screenRow >= 0
|
||
bufferRow = @activeEditSession.bufferPositionForScreenPosition([screenRow]).row
|
||
bufferLine = @activeEditSession.lineForBufferRow(bufferRow)
|
||
unless bufferLine is ''
|
||
guideIndentation = Math.ceil(@activeEditSession.indentLevelForLine(bufferLine))
|
||
break
|
||
|
||
if indentation > 0
|
||
indentationHtml = "<span class='indent-guide'>#{_.multiplyString(' ', @activeEditSession.getTabLength())}</span>"
|
||
return _.multiplyString(indentationHtml, indentation)
|
||
|
||
return ' ' unless @showInvisibles
|
||
|
||
@buildLineHtmlHelper: ({tokens, text, lineEnding, fold, isSoftWrapped, attributes, guideIndentation, showInvisibles}) ->
|
||
scopeStack = []
|
||
line = []
|
||
|
||
updateScopeStack = (desiredScopes) ->
|
||
excessScopes = scopeStack.length - desiredScopes.length
|
||
_.times(excessScopes, popScope) if excessScopes > 0
|
||
|
||
# pop until common prefix
|
||
for i in [scopeStack.length..0]
|
||
break if _.isEqual(scopeStack[0...i], desiredScopes[0...i])
|
||
popScope()
|
||
|
||
# push on top of common prefix until scopeStack == desiredScopes
|
||
for j in [i...desiredScopes.length]
|
||
pushScope(desiredScopes[j])
|
||
|
||
pushScope = (scope) ->
|
||
scopeStack.push(scope)
|
||
line.push("<span class=\"#{scope.replace(/\./g, ' ')}\">")
|
||
|
||
popScope = ->
|
||
scopeStack.pop()
|
||
line.push("</span>")
|
||
|
||
attributePairs = []
|
||
attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes
|
||
line.push("<div #{attributePairs.join(' ')}>")
|
||
|
||
invisibles = @invisibles if showInvisibles
|
||
|
||
if screenLine.text == ''
|
||
html = @buildEmptyLineHtml(screenRow)
|
||
line.push(html) if html
|
||
else
|
||
firstNonWhitespacePosition = text.search(/\S/)
|
||
firstTrailingWhitespacePosition = text.search(/\s*$/)
|
||
lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0
|
||
position = 0
|
||
for token in tokens
|
||
updateScopeStack(token.scopes)
|
||
hasLeadingWhitespace = position < firstNonWhitespacePosition
|
||
hasTrailingWhitespace = position + token.value.length > firstTrailingWhitespacePosition
|
||
hasIndentGuide = not @mini and guideIndentation? and (hasLeadingWhitespace or lineIsWhitespaceOnly)
|
||
line.push(token.getValueAsHtml({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide}))
|
||
position += token.value.length
|
||
|
||
popScope() while scopeStack.length > 0
|
||
if invisibles and not @mini and not screenLine.isSoftWrapped()
|
||
if invisibles.cr and screenLine.lineEnding is '\r\n'
|
||
line.push("<span class='invisible-character'>#{invisibles.cr}</span>")
|
||
if invisibles.eol
|
||
line.push("<span class='invisible-character'>#{invisibles.eol}</span>")
|
||
|
||
line.push("<span class='fold-marker'/>") if fold
|
||
|
||
line.push('</div>')
|
||
line.join('')
|
||
|
||
lineElementForScreenRow: (screenRow) ->
|
||
@renderedLines.children(":eq(#{screenRow - @firstRenderedScreenRow})")
|
||
|
||
toggleLineCommentsInSelection: ->
|
||
@activeEditSession.toggleLineCommentsInSelection()
|
||
|
||
### Public ###
|
||
|
||
# Converts a buffer position to a pixel position.
|
||
#
|
||
# position - An object that represents a buffer position. It can be either
|
||
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
||
#
|
||
# Returns an object with two values: `top` and `left`, representing the pixel positions.
|
||
pixelPositionForBufferPosition: (position) ->
|
||
@pixelPositionForScreenPosition(@screenPositionForBufferPosition(position))
|
||
|
||
# Converts a screen position to a pixel position.
|
||
#
|
||
# position - An object that represents a screen position. It can be either
|
||
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
||
#
|
||
# Returns an object with two values: `top` and `left`, representing the pixel positions.
|
||
pixelPositionForScreenPosition: (position) ->
|
||
return { top: 0, left: 0 } unless @isOnDom() and @isVisible()
|
||
{row, column} = Point.fromObject(position)
|
||
actualRow = Math.floor(row)
|
||
|
||
lineElement = existingLineElement = @lineElementForScreenRow(actualRow)[0]
|
||
unless existingLineElement
|
||
lineElement = @buildLineElementForScreenRow(actualRow)
|
||
@renderedLines.append(lineElement)
|
||
left = @positionLeftForLineAndColumn(lineElement, column)
|
||
unless existingLineElement
|
||
@renderedLines[0].removeChild(lineElement)
|
||
{ top: row * @lineHeight, left }
|
||
|
||
positionLeftForLineAndColumn: (lineElement, column) ->
|
||
return 0 if column is 0
|
||
delta = 0
|
||
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, acceptNode: -> NodeFilter.FILTER_ACCEPT)
|
||
while textNode = iterator.nextNode()
|
||
nextDelta = delta + textNode.textContent.length
|
||
if nextDelta >= column
|
||
offset = column - delta
|
||
break
|
||
delta = nextDelta
|
||
|
||
range = document.createRange()
|
||
range.setEnd(textNode, offset)
|
||
range.collapse()
|
||
leftPixels = range.getClientRects()[0].left - @scrollView.offset().left + @scrollView.scrollLeft()
|
||
range.detach()
|
||
leftPixels
|
||
|
||
pixelOffsUtilsetForScreenPosition: (position) ->
|
||
{top, left} = @pixelPositionForScreenPosition(position)
|
||
offset = @renderedLines.offset()
|
||
{top: top + offset.top, left: left + offset.left}
|
||
|
||
screenPositionFromMouseEvent: (e) ->
|
||
{ pageX, pageY } = e
|
||
|
||
editorRelativeTop = pageY - @scrollView.offset().top + @scrollTop()
|
||
row = Math.floor(editorRelativeTop / @lineHeight)
|
||
column = 0
|
||
|
||
if lineElement = @lineElementForScreenRow(row)[0]
|
||
range = document.createRange()
|
||
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, acceptNode: -> NodeFilter.FILTER_ACCEPT)
|
||
while node = iterator.nextNode()
|
||
range.selectNodeContents(node)
|
||
column += node.textContent.length
|
||
{left, right} = range.getClientRects()[0]
|
||
break if left <= pageX <= right
|
||
|
||
if node
|
||
for characterPosition in [node.textContent.length...0]
|
||
range.setStart(node, characterPosition - 1)
|
||
range.setEnd(node, characterPosition)
|
||
{left, right, width} = range.getClientRects()[0]
|
||
break if left <= pageX - width / 2 <= right
|
||
column--
|
||
|
||
range.detach()
|
||
|
||
new Point(row, column)
|
||
|
||
# Highlights the current line the cursor is on.
|
||
highlightCursorLine: ->
|
||
return if @mini
|
||
|
||
@highlightedLine?.removeClass('cursor-line')
|
||
if @getSelection().isEmpty()
|
||
@highlightedLine = @lineElementForScreenRow(@getCursorScreenRow())
|
||
@highlightedLine.addClass('cursor-line')
|
||
else
|
||
@highlightedLine = null
|
||
|
||
# {Delegates to: EditSession.getGrammar}
|
||
getGrammar: ->
|
||
@activeEditSession.getGrammar()
|
||
|
||
# {Delegates to: EditSession.setGrammar}
|
||
setGrammar: (grammar) ->
|
||
throw new Error("Only mini-editors can explicity set their grammar") unless @mini
|
||
@activeEditSession.setGrammar(grammar)
|
||
|
||
# {Delegates to: EditSession.reloadGrammar}
|
||
reloadGrammar: ->
|
||
@activeEditSession.reloadGrammar()
|
||
|
||
# Copies the current file path to the native clipboard.
|
||
copyPathToPasteboard: ->
|
||
path = @getPath()
|
||
pasteboard.write(path) if path?
|
||
|
||
### Internal ###
|
||
|
||
bindToKeyedEvent: (key, event, callback) ->
|
||
binding = {}
|
||
binding[key] = event
|
||
window.keymap.bindKeys '.editor', binding
|
||
@on event, =>
|
||
callback(this, event)
|
||
|
||
replaceSelectedText: (replaceFn) ->
|
||
selection = @getSelection()
|
||
return false if selection.isEmpty()
|
||
|
||
text = replaceFn(@getTextInRange(selection.getBufferRange()))
|
||
return false if text is null or text is undefined
|
||
|
||
@insertText(text, select: true)
|
||
true
|
||
|
||
consolidateSelections: (e) -> e.abortKeyBinding() unless @activeEditSession.consolidateSelections()
|
||
|
||
logCursorScope: ->
|
||
console.log @activeEditSession.getCursorScopes()
|
||
|
||
transact: (fn) -> @activeEditSession.transact(fn)
|
||
commit: -> @activeEditSession.commit()
|
||
abort: -> @activeEditSession.abort()
|
||
|
||
saveDebugSnapshot: ->
|
||
atom.showSaveDialog (path) =>
|
||
fsUtils.write(path, @getDebugSnapshot()) if path
|
||
|
||
getDebugSnapshot: ->
|
||
[
|
||
"Debug Snapshot: #{@getPath()}"
|
||
@getRenderedLinesDebugSnapshot()
|
||
@activeEditSession.getDebugSnapshot()
|
||
@getBuffer().getDebugSnapshot()
|
||
].join('\n\n')
|
||
|
||
getRenderedLinesDebugSnapshot: ->
|
||
lines = ['Rendered Lines:']
|
||
firstRenderedScreenRow = @firstRenderedScreenRow
|
||
@renderedLines.find('.line').each (n) ->
|
||
lines.push "#{firstRenderedScreenRow + n}: #{$(this).text()}"
|
||
lines.join('\n')
|
||
|
||
logScreenLines: (start, end) ->
|
||
@activeEditSession.logScreenLines(start, end)
|
||
|
||
logRenderedLines: ->
|
||
@renderedLines.find('.line').each (n) ->
|
||
console.log n, $(this).text()
|