mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
84
src/text-editor-element.coffee
Normal file
84
src/text-editor-element.coffee
Normal 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'
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user