Add TextEditorElement and make TextEditorView a wrapper around it

This is the next step on converting all internal views to custom
elements instead of using SpacePen. The TextEditorElement instances
are associated with ::__spacePenView fields that are used for supporting
legacy access paths via atom.workspaceView.
This commit is contained in:
Nathan Sobo
2014-10-01 17:11:09 -06:00
parent 541c140a19
commit 003b67ee19
5 changed files with 160 additions and 103 deletions

View File

@@ -7,6 +7,8 @@ PaneElement = require './pane-element'
PaneContainerElement = require './pane-container-element'
PaneAxisElement = require './pane-axis-element'
PaneAxis = require './pane-axis'
TextEditor = require './text-editor'
TextEditorElement = require './text-editor-element'
ViewRegistry = require './view-registry'
ItemRegistry = require './item-registry'
@@ -66,6 +68,10 @@ class PaneContainer extends Model
modelConstructor: Pane
viewConstructor: PaneElement
@viewRegistry.addViewProvider
modelConstructor: TextEditor
viewConstructor: TextEditorElement
getView: (object) ->
@viewRegistry.getView(object)

View File

@@ -193,7 +193,7 @@ TextEditorComponent = React.createClass
componentWillUnmount: ->
{editor, parentView} = @props
parentView.trigger 'editor:will-be-removed', [parentView]
parentView.__spacePenView.trigger 'editor:will-be-removed', [parentView.__spacePenView]
@unsubscribe()
window.removeEventListener 'resize', @requestHeightAndWidthMeasurement
clearInterval(@domPollingIntervalId)
@@ -212,9 +212,9 @@ TextEditorComponent = React.createClass
if @props.editor.isAlive()
@updateParentViewFocusedClassIfNeeded(prevState)
@updateParentViewMiniClassIfNeeded(prevState)
@props.parentView.trigger 'cursor:moved' if cursorMoved
@props.parentView.trigger 'selection:changed' if selectionChanged
@props.parentView.trigger 'editor:display-updated'
@props.parentView.__spacePenView.trigger 'cursor:moved' if cursorMoved
@props.parentView.__spacePenView.trigger 'selection:changed' if selectionChanged
@props.parentView.__spacePenView.trigger 'editor:display-updated'
becameVisible: ->
@updatesPaused = true
@@ -255,7 +255,7 @@ TextEditorComponent = React.createClass
@forceUpdate()
getTopmostDOMNode: ->
@props.parentView.element
@props.parentView
getRenderedRowRange: ->
{editor, lineOverdrawMargin} = @props
@@ -515,7 +515,7 @@ TextEditorComponent = React.createClass
{parentView} = @props
addListener = (command, listener) =>
@subscribe parentView.command command, (event) ->
@subscribe parentView.__spacePenView.command command, (event) ->
event.stopPropagation()
listener(event)
@@ -864,10 +864,9 @@ TextEditorComponent = React.createClass
return unless @isMounted()
{editor, parentView} = @props
parentNode = parentView.element
scrollViewNode = @refs.scrollView.getDOMNode()
{position} = getComputedStyle(parentNode)
{height} = parentNode.style
{position} = getComputedStyle(parentView)
{height} = parentView.style
if position is 'absolute' or height
if @autoHeight
@@ -901,7 +900,7 @@ TextEditorComponent = React.createClass
sampleBackgroundColors: (suppressUpdate) ->
{parentView} = @props
{showLineNumbers} = @state
{backgroundColor} = getComputedStyle(parentView.element)
{backgroundColor} = getComputedStyle(parentView)
if backgroundColor isnt @backgroundColor
@backgroundColor = backgroundColor
@@ -1070,11 +1069,11 @@ TextEditorComponent = React.createClass
updateParentViewFocusedClassIfNeeded: (prevState) ->
if prevState.focused isnt @state.focused
@props.parentView.toggleClass('is-focused', @props.focused)
@props.parentView.classList.toggle('is-focused', @state.focused)
updateParentViewMiniClassIfNeeded: (prevProps) ->
if prevProps.mini isnt @props.mini
@props.parentView.toggleClass('mini', @props.mini)
@props.parentView.classList.toggle('mini', @props.mini)
runScrollBenchmark: ->
unless process.env.NODE_ENV is 'production'

View File

@@ -0,0 +1,84 @@
{View, $} = require 'space-pen'
React = require 'react-atom-fork'
{defaults} = require 'underscore-plus'
TextBuffer = require 'text-buffer'
TextEditor = require './text-editor'
TextEditorComponent = require './text-editor-component'
TextEditorView = null
class TextEditorElement extends HTMLElement
model: null
componentDescriptor: null
component: null
lineOverdrawMargin: null
focusOnAttach: false
createdCallback: ->
@subscriptions =
@initializeContent()
@createSpacePenShim()
@addEventListener 'focus', @focused.bind(this)
initializeContent: (attributes) ->
@classList.add('editor', 'react', 'editor-colors')
@setAttribute('tabindex', -1)
createSpacePenShim: ->
TextEditorView ?= require './text-editor-view'
@__spacePenView = new TextEditorView(this)
attachedCallback: ->
@buildModel() unless @getModel()?
@mountComponent() unless @component?.isMounted()
@component.checkForVisibilityChange()
@focus() if @focusOnAttach
setModel: (model) ->
throw new Error("Model already assigned on TextEditorElement") if @model?
@model = model
@mountComponent()
@addGrammarScopeAttribute()
@model.onDidChangeGrammar => @addGrammarScopeAttribute()
@__spacePenView.setModel(@model)
@model
getModel: ->
@model ? @buildModel()
buildModel: ->
@setModel(new TextEditor(
buffer: new TextBuffer
softWrapped: false
tabLength: 2
softTabs: true
mini: @getAttribute('mini')
placeholderText: placeholderText
))
mountComponent: ->
@componentDescriptor ?= TextEditorComponent(
parentView: this
editor: @model
mini: @model.mini
lineOverdrawMargin: @lineOverdrawMargin
)
@component = React.renderComponent(@componentDescriptor, this)
unmountComponent: ->
return unless @component?.isMounted()
React.unmountComponentAtNode(this)
@component = null
focused: ->
if @component?
@component.onFocus()
else
@focusOnAttach = true
addGrammarScopeAttribute: ->
grammarScope = @model.getGrammar()?.scopeName?.replace(/\./g, ' ')
@setAttribute('data-grammar', grammarScope)
module.exports = TextEditorElement = document.registerElement 'atom-text-editor',
prototype: TextEditorElement.prototype
extends: 'div'

View File

@@ -3,6 +3,7 @@ React = require 'react-atom-fork'
{defaults} = require 'underscore-plus'
TextBuffer = require 'text-buffer'
TextEditor = require './text-editor'
TextEditorElement = require './text-editor-element'
TextEditorComponent = require './text-editor-component'
{deprecate} = require 'grim'
@@ -37,51 +38,47 @@ TextEditorComponent = require './text-editor-component'
# ```
module.exports =
class TextEditorView extends View
@content: (params) ->
attributes = params.attributes ? {}
attributes.class = 'editor react editor-colors'
attributes.tabIndex = -1
@div attributes
focusOnAttach: false
# The constructor for setting up an `TextEditorView` instance.
#
# * `editorOrParams` Either an {TextEditor}, or an object with one property, `mini`.
# * `modelOrParams` Either an {TextEditor}, or an object with one property, `mini`.
# If `mini` is `true`, a "miniature" `TextEditor` 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._.
#
constructor: (editorOrParams, props) ->
constructor: (modelOrParams, props) ->
# Handle direct construction with an editor or params
unless modelOrParams instanceof HTMLElement
if modelOrParams instanceof TextEditor
model = modelOrParams
else
{editor, mini, placeholderText} = modelOrParams
model = editor ? new TextEditor
buffer: new TextBuffer
softWrapped: false
tabLength: 2
softTabs: true
mini: mini
placeholderText: placeholderText
element = new TextEditorElement
element.lineOverdrawMargin = props?.lineOverdrawMargin
element.setModel(model)
return element.__spacePenView
# Handle construction with an element
@element = modelOrParams
super
if editorOrParams instanceof TextEditor
@editor = editorOrParams
else
{@editor, mini, placeholderText} = editorOrParams
props ?= {}
props.mini = mini
@editor ?= new TextEditor
buffer: new TextBuffer
softWrapped: false
tabLength: 2
softTabs: true
mini: mini
placeholderText: placeholderText
setModel: (@model) ->
@editor = @model
props = defaults({@editor, parentView: this}, props)
@componentDescriptor = TextEditorComponent(props)
@component = React.renderComponent(@componentDescriptor, @element)
node = @component.getDOMNode()
@scrollView = $(node).find('.scroll-view')
@underlayer = $(node).find('.highlights').addClass('underlayer')
@overlayer = $(node).find('.lines').addClass('overlayer')
@hiddenInput = $(node).find('.hidden-input')
@scrollView = @find('.scroll-view')
@underlayer = @find('.highlights').addClass('underlayer')
@overlayer = @find('.lines').addClass('overlayer')
@hiddenInput = @.find('.hidden-input')
@subscribe atom.config.observe 'editor.showLineNumbers', =>
@gutter = $(node).find('.gutter')
@gutter = @find('.gutter')
@gutter.removeClassFromAllLines = (klass) =>
deprecate('Use decorations instead: http://blog.atom.io/2014/07/24/decorations.html')
@@ -97,99 +94,73 @@ class TextEditorView extends View
lines.addClass(klass)
lines.length > 0
@on 'focus', =>
if @component?
@component.onFocus()
else
@focusOnAttach = true
# Public: Get the underlying editor model for this view.
#
# Returns an {TextEditor}
getModel: -> @editor
getModel: -> @model
getEditor: -> @editor
getEditor: -> @model
Object.defineProperty @::, 'lineHeight', get: -> @editor.getLineHeightInPixels()
Object.defineProperty @::, 'charWidth', get: -> @editor.getDefaultCharWidth()
Object.defineProperty @::, 'lineHeight', get: -> @model.getLineHeightInPixels()
Object.defineProperty @::, 'charWidth', get: -> @model.getDefaultCharWidth()
Object.defineProperty @::, 'firstRenderedScreenRow', get: -> @component.getRenderedRowRange()[0]
Object.defineProperty @::, 'lastRenderedScreenRow', get: -> @component.getRenderedRowRange()[1]
Object.defineProperty @::, 'active', get: -> @is(@getPaneView()?.activeView)
Object.defineProperty @::, 'isFocused', get: -> @component?.state.focused
Object.defineProperty @::, 'mini', get: -> @component?.props.mini
Object.defineProperty @::, 'component', get: -> @element.component
afterAttach: (onDom) ->
return unless onDom
return if @attached
@attached = true
@component = React.renderComponent(@componentDescriptor, @element) unless @component.isMounted()
@component.checkForVisibilityChange()
@focus() if @focusOnAttach
@addGrammarScopeAttribute()
@subscribe @editor.onDidChangeGrammar => @addGrammarScopeAttribute()
@trigger 'editor:attached', [this]
addGrammarScopeAttribute: ->
grammarScope = @editor.getGrammar()?.scopeName?.replace(/\./g, ' ')
@attr('data-grammar', grammarScope)
beforeRemove: ->
@trigger 'editor:detached', [this]
@attached = false
scrollTop: (scrollTop) ->
if scrollTop?
@editor.setScrollTop(scrollTop)
@model.setScrollTop(scrollTop)
else
@editor.getScrollTop()
@model.getScrollTop()
scrollLeft: (scrollLeft) ->
if scrollLeft?
@editor.setScrollLeft(scrollLeft)
@model.setScrollLeft(scrollLeft)
else
@editor.getScrollLeft()
@model.getScrollLeft()
scrollToBottom: ->
deprecate 'Use TextEditor::scrollToBottom instead. You can get the editor via editorView.getModel()'
@editor.setScrollBottom(Infinity)
@model.setScrollBottom(Infinity)
scrollToScreenPosition: (screenPosition, options) ->
deprecate 'Use TextEditor::scrollToScreenPosition instead. You can get the editor via editorView.getModel()'
@editor.scrollToScreenPosition(screenPosition, options)
@model.scrollToScreenPosition(screenPosition, options)
scrollToBufferPosition: (bufferPosition, options) ->
deprecate 'Use TextEditor::scrollToBufferPosition instead. You can get the editor via editorView.getModel()'
@editor.scrollToBufferPosition(bufferPosition, options)
@model.scrollToBufferPosition(bufferPosition, options)
scrollToCursorPosition: ->
deprecate 'Use TextEditor::scrollToCursorPosition instead. You can get the editor via editorView.getModel()'
@editor.scrollToCursorPosition()
@model.scrollToCursorPosition()
pixelPositionForBufferPosition: (bufferPosition) ->
deprecate 'Use TextEditor::pixelPositionForBufferPosition instead. You can get the editor via editorView.getModel()'
@editor.pixelPositionForBufferPosition(bufferPosition)
@model.pixelPositionForBufferPosition(bufferPosition)
pixelPositionForScreenPosition: (screenPosition) ->
deprecate 'Use TextEditor::pixelPositionForScreenPosition instead. You can get the editor via editorView.getModel()'
@editor.pixelPositionForScreenPosition(screenPosition)
@model.pixelPositionForScreenPosition(screenPosition)
appendToLinesView: (view) ->
view.css('position', 'absolute')
view.css('z-index', 1)
@find('.lines').prepend(view)
detach: ->
return unless @attached
super
@attached = false
@unmountComponent()
beforeRemove: ->
return unless @attached
@attached = false
@unmountComponent()
@editor.destroy()
@trigger 'editor:detached', this
unmountComponent: ->
React.unmountComponentAtNode(@element) if @component.isMounted()
@@ -248,19 +219,19 @@ class TextEditorView extends View
pageDown: ->
deprecate('Use editorView.getModel().pageDown()')
@editor.pageDown()
@model.pageDown()
pageUp: ->
deprecate('Use editorView.getModel().pageUp()')
@editor.pageUp()
@model.pageUp()
getFirstVisibleScreenRow: ->
deprecate 'Use TextEditor::getFirstVisibleScreenRow instead. You can get the editor via editorView.getModel()'
@editor.getFirstVisibleScreenRow()
@model.getFirstVisibleScreenRow()
getLastVisibleScreenRow: ->
deprecate 'Use TextEditor::getLastVisibleScreenRow instead. You can get the editor via editorView.getModel()'
@editor.getLastVisibleScreenRow()
@model.getLastVisibleScreenRow()
getFontFamily: ->
deprecate 'This is going away. Use atom.config.get("editor.fontFamily") instead'
@@ -283,7 +254,7 @@ class TextEditorView extends View
@component.setLineHeight(lineHeight)
setWidthInChars: (widthInChars) ->
@component.getDOMNode().style.width = (@editor.getDefaultCharWidth() * widthInChars) + 'px'
@component.getDOMNode().style.width = (@model.getDefaultCharWidth() * widthInChars) + 'px'
setShowIndentGuide: (showIndentGuide) ->
deprecate 'This is going away. Use atom.config.set("editor.showIndentGuide", true|false) instead'
@@ -291,20 +262,20 @@ class TextEditorView extends View
setSoftWrap: (softWrapped) ->
deprecate 'Use TextEditor::setSoftWrapped instead. You can get the editor via editorView.getModel()'
@editor.setSoftWrapped(softWrapped)
@model.setSoftWrapped(softWrapped)
setShowInvisibles: (showInvisibles) ->
deprecate 'This is going away. Use atom.config.set("editor.showInvisibles", true|false) instead'
@component.setShowInvisibles(showInvisibles)
getText: ->
@editor.getText()
@model.getText()
setText: (text) ->
@editor.setText(text)
@model.setText(text)
insertText: (text) ->
@editor.insertText(text)
@model.insertText(text)
isInputEnabled: ->
@component.isInputEnabled()
@@ -326,7 +297,7 @@ class TextEditorView extends View
setPlaceholderText: (placeholderText) ->
deprecate('Use TextEditor::setPlaceholderText instead. eg. editorView.getModel().setPlaceholderText(text)')
@getModel().setPlaceholderText(placeholderText)
@model.setPlaceholderText(placeholderText)
lineElementForScreenRow: (screenRow) ->
$(@component.lineNodeForScreenRow(screenRow))

View File

@@ -162,9 +162,6 @@ class TextEditor extends Model
@subscribe @displayBuffer.onDidAddDecoration (decoration) => @emit 'decoration-added', decoration
@subscribe @displayBuffer.onDidRemoveDecoration (decoration) => @emit 'decoration-removed', decoration
getViewClass: ->
require './text-editor-view'
destroyed: ->
@unsubscribe()
selection.destroy() for selection in @getSelections()