From 003b67ee19a8efc6c30a6ac8ab689e12822c3703 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Oct 2014 17:11:09 -0600 Subject: [PATCH] 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. --- src/pane-container.coffee | 6 ++ src/text-editor-component.coffee | 23 +++-- src/text-editor-element.coffee | 84 ++++++++++++++++++ src/text-editor-view.coffee | 147 +++++++++++++------------------ src/text-editor.coffee | 3 - 5 files changed, 160 insertions(+), 103 deletions(-) create mode 100644 src/text-editor-element.coffee diff --git a/src/pane-container.coffee b/src/pane-container.coffee index aa9f17d97..85f524fa3 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -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) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 249a15d67..28ecb99ba 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -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' diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee new file mode 100644 index 000000000..7b4b43c67 --- /dev/null +++ b/src/text-editor-element.coffee @@ -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' diff --git a/src/text-editor-view.coffee b/src/text-editor-view.coffee index b84a8adcf..cd1c0152e 100644 --- a/src/text-editor-view.coffee +++ b/src/text-editor-view.coffee @@ -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)) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 2bd07102a..a73207cdd 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -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()