mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Render the entire editor with React. Handle vertical scrolling.
The space-pen view is now a simple wrapper around the entire React component to integrate it cleanly into our existing system. React components can't adopt existing DOM nodes, otherwise I would just have the react component take over the entire view instead of wrapping.
This commit is contained in:
97
src/editor-component.coffee
Normal file
97
src/editor-component.coffee
Normal file
@@ -0,0 +1,97 @@
|
||||
{React, div, span} = require 'reactionary'
|
||||
{last} = require 'underscore-plus'
|
||||
{$$} = require 'space-pencil'
|
||||
|
||||
DummyLineNode = $$ ->
|
||||
@div class: 'line', style: 'position: absolute; visibility: hidden;', -> @span 'x'
|
||||
|
||||
module.exports =
|
||||
React.createClass
|
||||
render: ->
|
||||
div class: 'editor',
|
||||
div class: 'scroll-view', ref: 'scrollView',
|
||||
div @renderVisibleLines()
|
||||
div class: 'vertical-scrollbar', ref: 'verticalScrollbar', onScroll: @onVerticalScroll,
|
||||
div outlet: 'verticalScrollbarContent', style: {height: @getScrollHeight()}
|
||||
|
||||
renderVisibleLines: ->
|
||||
[startRow, endRow] = @getVisibleRowRange()
|
||||
precedingHeight = startRow * @state.lineHeight
|
||||
lineCount = @props.editor.getScreenLineCount()
|
||||
followingHeight = (lineCount - endRow) * @state.lineHeight
|
||||
|
||||
div class: 'lines', ref: 'lines', style: {top: -@state.scrollTop},
|
||||
div class: 'spacer', style: {height: precedingHeight}
|
||||
for tokenizedLine in @props.editor.linesForScreenRows(startRow, endRow - 1)
|
||||
LineComponent({tokenizedLine, key: tokenizedLine.id})
|
||||
div class: 'spacer', style: {height: followingHeight}
|
||||
|
||||
getInitialState: ->
|
||||
height: 0
|
||||
width: 0
|
||||
lineHeight: 0
|
||||
scrollTop: 0
|
||||
|
||||
componentDidMount: ->
|
||||
@props.editor.on 'screen-lines-changed', @onScreenLinesChanged
|
||||
@updateAllDimensions()
|
||||
|
||||
componentWillUnmount: ->
|
||||
@props.editor.off 'screen-lines-changed', @onScreenLinesChanged
|
||||
|
||||
componentWilUpdate: (nextProps, nextState) ->
|
||||
if nextState.scrollTop?
|
||||
@refs.verticalScrollbar.getDOMNode().scrollTop = nextState.scrollTop
|
||||
|
||||
onVerticalScroll: ->
|
||||
scrollTop = @refs.verticalScrollbar.getDOMNode().scrollTop
|
||||
@setState({scrollTop})
|
||||
|
||||
onScreenLinesChanged: ({start, end}) =>
|
||||
[visibleStart, visibleEnd] = @getVisibleRowRange()
|
||||
@forceUpdate() unless end < visibleStart or visibleEnd <= start
|
||||
|
||||
getVisibleRowRange: ->
|
||||
return [0, 0] unless @state.lineHeight > 0
|
||||
|
||||
heightInLines = @state.height / @state.lineHeight
|
||||
startRow = Math.floor(@state.scrollTop / @state.lineHeight)
|
||||
endRow = Math.ceil(startRow + heightInLines)
|
||||
[startRow, endRow]
|
||||
|
||||
getScrollHeight: ->
|
||||
@props.editor.getLineCount() * @state.lineHeight
|
||||
|
||||
updateAllDimensions: ->
|
||||
lineHeight = @measureLineHeight()
|
||||
{height, width} = @measureScrollViewDimensions()
|
||||
|
||||
console.log "updating dimensions", {lineHeight, height, width}
|
||||
|
||||
@setState({lineHeight, height, width})
|
||||
|
||||
measureScrollViewDimensions: ->
|
||||
scrollViewNode = @refs.scrollView.getDOMNode()
|
||||
{height: scrollViewNode.clientHeight, width: scrollViewNode.clientWidth}
|
||||
|
||||
measureLineHeight: ->
|
||||
linesNode = @refs.lines.getDOMNode()
|
||||
linesNode.appendChild(DummyLineNode)
|
||||
lineHeight = DummyLineNode.getBoundingClientRect().height
|
||||
linesNode.removeChild(DummyLineNode)
|
||||
lineHeight
|
||||
|
||||
LineComponent = React.createClass
|
||||
render: ->
|
||||
div class: 'line',
|
||||
if @props.tokenizedLine.text.length is 0
|
||||
span String.fromCharCode(160) # non-breaking space; bypasses escaping
|
||||
else
|
||||
@renderScopeTree(@props.tokenizedLine.getScopeTree())
|
||||
|
||||
renderScopeTree: (scopeTree) ->
|
||||
if scopeTree.scope?
|
||||
span class: scopeTree.scope.split('.').join(' '),
|
||||
scopeTree.children.map (child) => @renderScopeTree(child)
|
||||
else
|
||||
span scopeTree.value
|
||||
@@ -1,45 +0,0 @@
|
||||
{React, div, span} = require 'reactionary'
|
||||
{last} = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
React.createClass
|
||||
render: ->
|
||||
div class: 'lines', @renderVisibleLines()
|
||||
|
||||
renderVisibleLines: ->
|
||||
return [] unless @props.lineHeight > 0
|
||||
|
||||
[startRow, endRow] = @getVisibleRowRange()
|
||||
for tokenizedLine in @props.editor.linesForScreenRows(startRow, endRow - 1)
|
||||
LineComponent({tokenizedLine, key: tokenizedLine.id})
|
||||
|
||||
getDefaultProps: ->
|
||||
height: 0
|
||||
lineHeight: 0
|
||||
scrollTop: 0
|
||||
|
||||
getVisibleRowRange: ->
|
||||
heightInLines = @props.height / @props.lineHeight
|
||||
startRow = Math.floor(@props.scrollTop / @props.lineHeight)
|
||||
endRow = Math.ceil(startRow + heightInLines)
|
||||
[startRow, endRow]
|
||||
|
||||
onScreenLinesChanged: ({start, end}) ->
|
||||
[visibleStart, visibleEnd] = @getVisibleRowRange()
|
||||
@forceUpdate() unless end < visibleStart or visibleEnd <= start
|
||||
|
||||
LineComponent = React.createClass
|
||||
render: ->
|
||||
{tokenizedLine} = @props
|
||||
div class: 'line',
|
||||
if tokenizedLine.text.length is 0
|
||||
span {}, String.fromCharCode(160) # non-breaking space; bypasses escaping
|
||||
else
|
||||
@renderScopeTree(tokenizedLine.getScopeTree())
|
||||
|
||||
renderScopeTree: (scopeTree) ->
|
||||
if scopeTree.scope?
|
||||
span class: scopeTree.scope.split('.').join(' '),
|
||||
scopeTree.children.map (child) => @renderScopeTree(child)
|
||||
else
|
||||
span scopeTree.value
|
||||
@@ -4,7 +4,6 @@ GutterView = require './gutter-view'
|
||||
Editor = require './editor'
|
||||
CursorView = require './cursor-view'
|
||||
SelectionView = require './selection-view'
|
||||
EditorContentsComponent = require './editor-contents-component'
|
||||
|
||||
fs = require 'fs-plus'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
@@ -1,43 +1,18 @@
|
||||
{View} = require 'space-pen'
|
||||
{$$} = require 'space-pencil'
|
||||
{React} = require 'reactionary'
|
||||
EditorContentsComponent = require './editor-contents-component'
|
||||
EditorComponent = require './editor-component'
|
||||
|
||||
module.exports =
|
||||
class ReactEditorView extends View
|
||||
@content: ->
|
||||
@div class: 'editor', =>
|
||||
@div class: 'scroll-view', outlet: 'scrollView'
|
||||
@content: -> @div class: 'react-wrapper'
|
||||
|
||||
constructor: (@editor) ->
|
||||
super
|
||||
@scrollView = @scrollView.element
|
||||
@contents = React.renderComponent(EditorContentsComponent({@editor}), @scrollView)
|
||||
@subscribe @editor, 'screen-lines-changed', (change) => @contents.onScreenLinesChanged(change)
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
return unless onDom
|
||||
@component = React.renderComponent(EditorComponent({@editor}), @element)
|
||||
|
||||
@editor.setVisible(true)
|
||||
|
||||
@measureLineHeight()
|
||||
@contents.setProps
|
||||
height: @scrollView.clientHeight
|
||||
scrollTop: @scrollView.scrollTop
|
||||
lineHeight: @lineHeight
|
||||
|
||||
setScrollTop: (scrollTop) ->
|
||||
@contents.setProps({scrollTop})
|
||||
|
||||
measureLineHeight: ->
|
||||
fragment = $$ ->
|
||||
@div class: 'lines', ->
|
||||
@div class: 'line', style: 'position: absolute; visibility: hidden;', -> @span 'x'
|
||||
|
||||
@scrollView.appendChild(fragment)
|
||||
lineRect = fragment.firstChild.getBoundingClientRect()
|
||||
charRect = fragment.firstChild.firstChild.getBoundingClientRect()
|
||||
@lineHeight = lineRect.height
|
||||
@charWidth = charRect.width
|
||||
@charHeight = charRect.height
|
||||
@scrollView.removeChild(fragment)
|
||||
beforeDetach: ->
|
||||
React.unmountComponentAtNode(@element)
|
||||
|
||||
Reference in New Issue
Block a user