mirror of
https://github.com/atom/atom.git
synced 2026-01-23 05:48:10 -05:00
339 lines
13 KiB
CoffeeScript
339 lines
13 KiB
CoffeeScript
React = require 'react'
|
|
{div, span} = require 'reactionary'
|
|
{debounce} = require 'underscore-plus'
|
|
|
|
GutterComponent = require './gutter-component'
|
|
EditorScrollViewComponent = require './editor-scroll-view-component'
|
|
ScrollbarComponent = require './scrollbar-component'
|
|
SubscriberMixin = require './subscriber-mixin'
|
|
|
|
module.exports =
|
|
EditorComponent = React.createClass
|
|
displayName: 'EditorComponent'
|
|
mixins: [SubscriberMixin]
|
|
|
|
pendingScrollTop: null
|
|
pendingScrollLeft: null
|
|
selectOnMouseMove: false
|
|
batchingUpdates: false
|
|
updateRequested: false
|
|
cursorsMoved: false
|
|
preservedRowRange: null
|
|
scrollingVertically: false
|
|
|
|
render: ->
|
|
{focused, fontSize, lineHeight, fontFamily, showIndentGuide} = @state
|
|
{editor, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
|
|
if @isMounted()
|
|
renderedRowRange = @getRenderedRowRange()
|
|
scrollHeight = editor.getScrollHeight()
|
|
scrollWidth = editor.getScrollWidth()
|
|
scrollTop = editor.getScrollTop()
|
|
scrollLeft = editor.getScrollLeft()
|
|
lineHeightInPixels = editor.getLineHeight()
|
|
|
|
className = 'editor editor-colors react'
|
|
className += ' is-focused' if focused
|
|
|
|
div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1,
|
|
GutterComponent {
|
|
editor, renderedRowRange, scrollTop, scrollHeight,
|
|
lineHeight: lineHeightInPixels, @pendingChanges
|
|
}
|
|
|
|
EditorScrollViewComponent {
|
|
ref: 'scrollView', editor, fontSize, fontFamily, showIndentGuide
|
|
scrollHeight, scrollWidth, lineHeight: lineHeightInPixels,
|
|
renderedRowRange, @pendingChanges, @scrollingVertically, @cursorsMoved,
|
|
cursorBlinkPeriod, cursorBlinkResumeDelay, @onInputFocused, @onInputBlurred
|
|
}
|
|
|
|
ScrollbarComponent
|
|
ref: 'verticalScrollbar'
|
|
className: 'vertical-scrollbar'
|
|
orientation: 'vertical'
|
|
onScroll: @onVerticalScroll
|
|
scrollTop: scrollTop
|
|
scrollHeight: scrollHeight
|
|
|
|
ScrollbarComponent
|
|
ref: 'horizontalScrollbar'
|
|
className: 'horizontal-scrollbar'
|
|
orientation: 'horizontal'
|
|
onScroll: @onHorizontalScroll
|
|
scrollLeft: scrollLeft
|
|
scrollWidth: scrollWidth
|
|
|
|
getRenderedRowRange: ->
|
|
renderedRowRange = @props.editor.getVisibleRowRange()
|
|
if @preservedRowRange?
|
|
renderedRowRange[0] = Math.min(@preservedRowRange[0], renderedRowRange[0])
|
|
renderedRowRange[1] = Math.max(@preservedRowRange[1], renderedRowRange[1])
|
|
renderedRowRange
|
|
|
|
getInitialState: -> {}
|
|
|
|
getDefaultProps: ->
|
|
cursorBlinkPeriod: 800
|
|
cursorBlinkResumeDelay: 200
|
|
|
|
componentWillMount: ->
|
|
@pendingChanges = []
|
|
@props.editor.manageScrollPosition = true
|
|
@observeConfig()
|
|
|
|
componentDidMount: ->
|
|
@observeEditor()
|
|
@listenForDOMEvents()
|
|
@listenForCommands()
|
|
@props.editor.setVisible(true)
|
|
@requestUpdate()
|
|
|
|
componentWillUnmount: ->
|
|
@unsubscribe()
|
|
@getDOMNode().removeEventListener 'mousewheel', @onMouseWheel
|
|
|
|
componentWillUpdate: ->
|
|
@props.parentView.trigger 'cursor:moved' if @cursorsMoved
|
|
|
|
componentDidUpdate: ->
|
|
@pendingChanges.length = 0
|
|
@cursorsMoved = false
|
|
@props.parentView.trigger 'editor:display-updated'
|
|
|
|
observeEditor: ->
|
|
{editor} = @props
|
|
@subscribe editor, 'batched-updates-started', @onBatchedUpdatesStarted
|
|
@subscribe editor, 'batched-updates-ended', @onBatchedUpdatesEnded
|
|
@subscribe editor, 'screen-lines-changed', @onScreenLinesChanged
|
|
@subscribe editor, 'cursors-moved', @onCursorsMoved
|
|
@subscribe editor, 'selection-screen-range-changed', @requestUpdate
|
|
@subscribe editor, 'selection-added', @onSelectionAdded
|
|
@subscribe editor, 'selection-removed', @onSelectionAdded
|
|
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
|
|
@subscribe editor.$scrollLeft.changes, @requestUpdate
|
|
@subscribe editor.$height.changes, @requestUpdate
|
|
@subscribe editor.$width.changes, @requestUpdate
|
|
@subscribe editor.$defaultCharWidth.changes, @requestUpdate
|
|
@subscribe editor.$lineHeight.changes, @requestUpdate
|
|
|
|
listenForDOMEvents: ->
|
|
node = @getDOMNode()
|
|
node.addEventListener 'mousewheel', @onMouseWheel
|
|
node.addEventListener 'focus', @onFocus # For some reason, React's built in focus events seem to bubble
|
|
|
|
listenForCommands: ->
|
|
{parentView, editor, mini} = @props
|
|
|
|
@addCommandListeners
|
|
'core:move-left': => editor.moveCursorLeft()
|
|
'core:move-right': => editor.moveCursorRight()
|
|
'core:select-left': => editor.selectLeft()
|
|
'core:select-right': => editor.selectRight()
|
|
'core:select-all': => editor.selectAll()
|
|
'core:backspace': => editor.backspace()
|
|
'core:delete': => editor.delete()
|
|
'core:undo': => editor.undo()
|
|
'core:redo': => editor.redo()
|
|
'core:cut': => editor.cutSelectedText()
|
|
'core:copy': => editor.copySelectedText()
|
|
'core:paste': => editor.pasteText()
|
|
'editor:move-to-previous-word': => editor.moveCursorToPreviousWord()
|
|
'editor:select-word': => editor.selectWord()
|
|
'editor:consolidate-selections': @consolidateSelections
|
|
'editor:backspace-to-beginning-of-word': => editor.backspaceToBeginningOfWord()
|
|
'editor:backspace-to-beginning-of-line': => editor.backspaceToBeginningOfLine()
|
|
'editor:delete-to-end-of-word': => editor.deleteToEndOfWord()
|
|
'editor:delete-line': => editor.deleteLine()
|
|
'editor:cut-to-end-of-line': => editor.cutToEndOfLine()
|
|
'editor:move-to-beginning-of-screen-line': => editor.moveCursorToBeginningOfScreenLine()
|
|
'editor:move-to-beginning-of-line': => editor.moveCursorToBeginningOfLine()
|
|
'editor:move-to-end-of-screen-line': => editor.moveCursorToEndOfScreenLine()
|
|
'editor:move-to-end-of-line': => editor.moveCursorToEndOfLine()
|
|
'editor:move-to-first-character-of-line': => editor.moveCursorToFirstCharacterOfLine()
|
|
'editor:move-to-beginning-of-word': => editor.moveCursorToBeginningOfWord()
|
|
'editor:move-to-end-of-word': => editor.moveCursorToEndOfWord()
|
|
'editor:move-to-beginning-of-next-word': => editor.moveCursorToBeginningOfNextWord()
|
|
'editor:move-to-previous-word-boundary': => editor.moveCursorToPreviousWordBoundary()
|
|
'editor:move-to-next-word-boundary': => editor.moveCursorToNextWordBoundary()
|
|
'editor:select-to-end-of-line': => editor.selectToEndOfLine()
|
|
'editor:select-to-beginning-of-line': => editor.selectToBeginningOfLine()
|
|
'editor:select-to-end-of-word': => editor.selectToEndOfWord()
|
|
'editor:select-to-beginning-of-word': => editor.selectToBeginningOfWord()
|
|
'editor:select-to-beginning-of-next-word': => editor.selectToBeginningOfNextWord()
|
|
'editor:select-to-next-word-boundary': => editor.selectToNextWordBoundary()
|
|
'editor:select-to-previous-word-boundary': => editor.selectToPreviousWordBoundary()
|
|
'editor:select-to-first-character-of-line': => editor.selectToFirstCharacterOfLine()
|
|
'editor:select-line': => editor.selectLine()
|
|
'editor:transpose': => editor.transpose()
|
|
'editor:upper-case': => editor.upperCase()
|
|
'editor:lower-case': => editor.lowerCase()
|
|
|
|
unless mini
|
|
@addCommandListeners
|
|
'core:move-up': => editor.moveCursorUp()
|
|
'core:move-down': => editor.moveCursorDown()
|
|
'core:move-to-top': => editor.moveCursorToTop()
|
|
'core:move-to-bottom': => editor.moveCursorToBottom()
|
|
'core:select-up': => editor.selectUp()
|
|
'core:select-down': => editor.selectDown()
|
|
'core:select-to-top': => editor.selectToTop()
|
|
'core:select-to-bottom': => editor.selectToBottom()
|
|
'editor:indent': => editor.indent()
|
|
'editor:auto-indent': => editor.autoIndentSelectedRows()
|
|
'editor:indent-selected-rows': => editor.indentSelectedRows()
|
|
'editor:outdent-selected-rows': => editor.outdentSelectedRows()
|
|
'editor:newline': => editor.insertNewline()
|
|
'editor:newline-below': => editor.insertNewlineBelow()
|
|
'editor:newline-above': => editor.insertNewlineAbove()
|
|
'editor:add-selection-below': => editor.addSelectionBelow()
|
|
'editor:add-selection-above': => editor.addSelectionAbove()
|
|
'editor:split-selections-into-lines': => editor.splitSelectionsIntoLines()
|
|
'editor:toggle-soft-tabs': => editor.toggleSoftTabs()
|
|
'editor:toggle-soft-wrap': => editor.toggleSoftWrap()
|
|
'editor:fold-all': => editor.foldAll()
|
|
'editor:unfold-all': => editor.unfoldAll()
|
|
'editor:fold-current-row': => editor.foldCurrentRow()
|
|
'editor:unfold-current-row': => editor.unfoldCurrentRow()
|
|
'editor:fold-selection': => neditor.foldSelectedLines()
|
|
'editor:fold-at-indent-level-1': => editor.foldAllAtIndentLevel(0)
|
|
'editor:fold-at-indent-level-2': => editor.foldAllAtIndentLevel(1)
|
|
'editor:fold-at-indent-level-3': => editor.foldAllAtIndentLevel(2)
|
|
'editor:fold-at-indent-level-4': => editor.foldAllAtIndentLevel(3)
|
|
'editor:fold-at-indent-level-5': => editor.foldAllAtIndentLevel(4)
|
|
'editor:fold-at-indent-level-6': => editor.foldAllAtIndentLevel(5)
|
|
'editor:fold-at-indent-level-7': => editor.foldAllAtIndentLevel(6)
|
|
'editor:fold-at-indent-level-8': => editor.foldAllAtIndentLevel(7)
|
|
'editor:fold-at-indent-level-9': => editor.foldAllAtIndentLevel(8)
|
|
'editor:toggle-line-comments': => editor.toggleLineCommentsInSelection()
|
|
'editor:log-cursor-scope': => editor.logCursorScope()
|
|
'editor:checkout-head-revision': => editor.checkoutHead()
|
|
'editor:copy-path': => editor.copyPathToClipboard()
|
|
'editor:move-line-up': => editor.moveLineUp()
|
|
'editor:move-line-down': => editor.moveLineDown()
|
|
'editor:duplicate-lines': => editor.duplicateLines()
|
|
'editor:join-lines': => editor.joinLines()
|
|
'editor:toggle-indent-guide': => atom.config.toggle('editor.showIndentGuide')
|
|
'editor:toggle-line-numbers': => atom.config.toggle('editor.showLineNumbers')
|
|
'editor:scroll-to-cursor': => editor.scrollToCursorPosition()
|
|
# 'core:page-down': => @pageDown()
|
|
# 'core:page-up': => @pageUp()
|
|
|
|
addCommandListeners: (listenersByCommandName) ->
|
|
{parentView} = @props
|
|
|
|
for command, listener of listenersByCommandName
|
|
parentView.command command, listener
|
|
|
|
observeConfig: ->
|
|
@subscribe atom.config.observe 'editor.fontFamily', @setFontFamily
|
|
@subscribe atom.config.observe 'editor.fontSize', @setFontSize
|
|
@subscribe atom.config.observe 'editor.showIndentGuide', @setShowIndentGuide
|
|
|
|
setFontSize: (fontSize) ->
|
|
@setState({fontSize})
|
|
|
|
setLineHeight: (lineHeight) ->
|
|
@setState({lineHeight})
|
|
|
|
setFontFamily: (fontFamily) ->
|
|
@setState({fontFamily})
|
|
|
|
setShowIndentGuide: (showIndentGuide) ->
|
|
@setState({showIndentGuide})
|
|
|
|
onFocus: ->
|
|
@refs.scrollView.focus()
|
|
|
|
onInputFocused: ->
|
|
@setState(focused: true)
|
|
|
|
onInputBlurred: ->
|
|
@setState(focused: false)
|
|
|
|
onVerticalScroll: (scrollTop) ->
|
|
{editor} = @props
|
|
|
|
return if scrollTop is editor.getScrollTop()
|
|
|
|
animationFramePending = @pendingScrollTop?
|
|
@pendingScrollTop = scrollTop
|
|
unless animationFramePending
|
|
requestAnimationFrame =>
|
|
@props.editor.setScrollTop(@pendingScrollTop)
|
|
@pendingScrollTop = null
|
|
|
|
onHorizontalScroll: (scrollLeft) ->
|
|
{editor} = @props
|
|
|
|
return if scrollLeft is editor.getScrollLeft()
|
|
|
|
animationFramePending = @pendingScrollLeft?
|
|
@pendingScrollLeft = scrollLeft
|
|
unless animationFramePending
|
|
requestAnimationFrame =>
|
|
@props.editor.setScrollLeft(@pendingScrollLeft)
|
|
@pendingScrollLeft = null
|
|
|
|
onMouseWheel: (event) ->
|
|
# Only scroll in one direction at a time
|
|
{wheelDeltaX, wheelDeltaY} = event
|
|
if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)
|
|
@refs.horizontalScrollbar.getDOMNode().scrollLeft -= wheelDeltaX
|
|
else
|
|
@refs.verticalScrollbar.getDOMNode().scrollTop -= wheelDeltaY
|
|
|
|
event.preventDefault()
|
|
|
|
clearPreservedRowRange: ->
|
|
@preservedRowRange = null
|
|
@scrollingVertically = false
|
|
@requestUpdate()
|
|
|
|
clearPreservedRowRangeAfterDelay: null # Created lazily
|
|
|
|
onBatchedUpdatesStarted: ->
|
|
@batchingUpdates = true
|
|
|
|
onBatchedUpdatesEnded: ->
|
|
updateRequested = @updateRequested
|
|
@updateRequested = false
|
|
@batchingUpdates = false
|
|
if updateRequested
|
|
@requestUpdate()
|
|
|
|
onScreenLinesChanged: (change) ->
|
|
{editor} = @props
|
|
@pendingChanges.push(change)
|
|
@requestUpdate() if editor.intersectsVisibleRowRange(change.start, change.end + 1) # TODO: Use closed-open intervals for change events
|
|
|
|
onSelectionAdded: (selection) ->
|
|
{editor} = @props
|
|
@requestUpdate() if editor.selectionIntersectsVisibleRowRange(selection)
|
|
|
|
onScrollTopChanged: ->
|
|
@preservedRowRange = @getRenderedRowRange()
|
|
@scrollingVertically = true
|
|
@clearPreservedRowRangeAfterDelay ?= debounce(@clearPreservedRowRange, 200)
|
|
@clearPreservedRowRangeAfterDelay()
|
|
@requestUpdate()
|
|
|
|
onSelectionRemoved: (selection) ->
|
|
{editor} = @props
|
|
@requestUpdate() if editor.selectionIntersectsVisibleRowRange(selection)
|
|
|
|
onCursorsMoved: ->
|
|
@cursorsMoved = true
|
|
|
|
requestUpdate: ->
|
|
if @batchingUpdates
|
|
@updateRequested = true
|
|
else
|
|
@forceUpdate()
|
|
|
|
measureHeightAndWidth: ->
|
|
@refs.scrollView.measureHeightAndWidth()
|
|
|
|
consolidateSelections: (e) ->
|
|
e.abortKeyBinding() unless @props.editor.consolidateSelections()
|