Merge pull request #3735 from atom/ns-text-editor-custom-element

Convert text editor to custom element
This commit is contained in:
Nathan Sobo 2014-10-08 14:12:35 -07:00
commit 079ea4862a
9 changed files with 327 additions and 227 deletions

View File

@ -97,7 +97,7 @@
"open-on-github": "0.30.0",
"package-generator": "0.31.0",
"release-notes": "0.36.0",
"settings-view": "0.149.0",
"settings-view": "0.150.0",
"snippets": "0.55.0",
"spell-check": "0.42.0",
"status-bar": "0.46.0",
@ -109,7 +109,7 @@
"update-package-dependencies": "0.6.0",
"welcome": "0.18.0",
"whitespace": "0.25.0",
"wrap-guide": "0.22.0",
"wrap-guide": "0.23.0",
"language-c": "0.28.0",
"language-coffee-script": "0.35.0",
"language-css": "0.20.0",

View File

@ -99,8 +99,6 @@ describe "CommandRegistry", ->
expect(dispatchedEvent.stopImmediatePropagation).toHaveBeenCalled()
it "forwards .preventDefault() calls from the synthetic event to the original", ->
calls = []
registry.add '.child', 'command', (event) -> event.preventDefault()
dispatchedEvent = new CustomEvent('command', bubbles: true)
@ -109,8 +107,6 @@ describe "CommandRegistry", ->
expect(dispatchedEvent.preventDefault).toHaveBeenCalled()
it "forwards .abortKeyBinding() calls from the synthetic event to the original", ->
calls = []
registry.add '.child', 'command', (event) -> event.abortKeyBinding()
dispatchedEvent = new CustomEvent('command', bubbles: true)

View File

@ -0,0 +1,36 @@
TextEditorElement = require '../src/text-editor-element'
# The rest of text-editor-component-spec will be moved to this file when React
# is eliminated. This covers only concerns related to the wrapper element for now
describe "TextEditorElement", ->
jasmineContent = null
beforeEach ->
jasmineContent = document.body.querySelector('#jasmine-content')
describe "instantiation", ->
it "honors the mini attribute", ->
jasmineContent.innerHTML = "<div is='atom-text-editor' mini>"
element = jasmineContent.firstChild
expect(element.getModel().isMini()).toBe true
it "honors the placeholder-text attribute", ->
jasmineContent.innerHTML = "<div is='atom-text-editor' placeholder-text='testing'>"
element = jasmineContent.firstChild
expect(element.getModel().getPlaceholderText()).toBe 'testing'
describe "::focus()", ->
it "transfers focus to the hidden text area and does not emit 'focusout' or 'blur' events", ->
element = new TextEditorElement
jasmineContent.appendChild(element)
focusoutCalled = false
element.addEventListener 'focusout', -> focusoutCalled = true
blurCalled = false
element.addEventListener 'blur', -> blurCalled = true
element.focus()
expect(focusoutCalled).toBe false
expect(blurCalled).toBe false
expect(element.hasFocus()).toBe true
expect(element.querySelector('input')).toBe document.activeElement

View File

@ -210,7 +210,7 @@ class CommandRegistry
loop
listeners = @inlineListenersByCommandName[originalEvent.type]?.get(currentTarget) ? []
unless currentTarget is window or currentTarget is document
if currentTarget.webkitMatchesSelector?
selectorBasedListeners =
(@selectorBasedListenersByCommandName[originalEvent.type] ? [])
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)

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

@ -176,7 +176,6 @@ TextEditorComponent = React.createClass
@observeEditor()
@listenForDOMEvents()
@listenForCommands()
@subscribe atom.themes.onDidAddStylesheet @onStylesheetsChanged
@subscribe atom.themes.onDidUpdateStylesheet @onStylesheetsChanged
@ -193,7 +192,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 +211,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 +254,7 @@ TextEditorComponent = React.createClass
@forceUpdate()
getTopmostDOMNode: ->
@props.parentView.element
@props.parentView
getRenderedRowRange: ->
{editor, lineOverdrawMargin} = @props
@ -406,123 +405,6 @@ TextEditorComponent = React.createClass
editor.insertText(selectedText, select: true, undo: 'skip')
event.target.value = ''
listenForCommands: ->
{parentView, editor, mini} = @props
@addCommandListeners
'core:move-left': -> editor.moveLeft()
'core:move-right': -> editor.moveRight()
'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.moveToPreviousWord()
'editor:select-word': -> editor.selectWordsContainingCursors()
'editor:consolidate-selections': @consolidateSelections
'editor:delete-to-beginning-of-word': -> editor.deleteToBeginningOfWord()
'editor:delete-to-beginning-of-line': -> editor.deleteToBeginningOfLine()
'editor:delete-to-end-of-line': -> editor.deleteToEndOfLine()
'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-next-paragraph': -> editor.moveToBeginningOfNextParagraph()
'editor:move-to-beginning-of-previous-paragraph': -> editor.moveToBeginningOfPreviousParagraph()
'editor:move-to-beginning-of-screen-line': -> editor.moveToBeginningOfScreenLine()
'editor:move-to-beginning-of-line': -> editor.moveToBeginningOfLine()
'editor:move-to-end-of-screen-line': -> editor.moveToEndOfScreenLine()
'editor:move-to-end-of-line': -> editor.moveToEndOfLine()
'editor:move-to-first-character-of-line': -> editor.moveToFirstCharacterOfLine()
'editor:move-to-beginning-of-word': -> editor.moveToBeginningOfWord()
'editor:move-to-end-of-word': -> editor.moveToEndOfWord()
'editor:move-to-beginning-of-next-word': -> editor.moveToBeginningOfNextWord()
'editor:move-to-previous-word-boundary': -> editor.moveToPreviousWordBoundary()
'editor:move-to-next-word-boundary': -> editor.moveToNextWordBoundary()
'editor:select-to-beginning-of-next-paragraph': -> editor.selectToBeginningOfNextParagraph()
'editor:select-to-beginning-of-previous-paragraph': -> editor.selectToBeginningOfPreviousParagraph()
'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.selectLinesContainingCursors()
'editor:transpose': -> editor.transpose()
'editor:upper-case': -> editor.upperCase()
'editor:lower-case': -> editor.lowerCase()
unless mini
@addCommandListeners
'core:move-up': -> editor.moveUp()
'core:move-down': -> editor.moveDown()
'core:move-to-top': -> editor.moveToTop()
'core:move-to-bottom': -> editor.moveToBottom()
'core:page-up': -> editor.pageUp()
'core:page-down': -> editor.pageDown()
'core:select-up': -> editor.selectUp()
'core:select-down': -> editor.selectDown()
'core:select-to-top': -> editor.selectToTop()
'core:select-to-bottom': -> editor.selectToBottom()
'core:select-page-up': -> editor.selectPageUp()
'core:select-page-down': -> editor.selectPageDown()
'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.toggleSoftWrapped()
'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': -> editor.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': -> atom.project.getRepositories()[0]?.checkoutHeadForEditor(editor)
'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.set('editor.showIndentGuide', not atom.config.get('editor.showIndentGuide'))
'editor:toggle-line-numbers': -> atom.config.set('editor.showLineNumbers', not atom.config.get('editor.showLineNumbers'))
'editor:scroll-to-cursor': -> editor.scrollToCursorPosition()
'benchmark:scroll': @runScrollBenchmark
addCommandListeners: (listenersByCommandName) ->
{parentView} = @props
addListener = (command, listener) =>
@subscribe parentView.command command, (event) ->
event.stopPropagation()
listener(event)
addListener(command, listener) for command, listener of listenersByCommandName
return
observeConfig: ->
@subscribe atom.config.observe 'editor.showIndentGuide', @setShowIndentGuide
@subscribe atom.config.observe 'editor.showLineNumbers', @setShowLineNumbers
@ -864,10 +746,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 +782,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 +951,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,206 @@
{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)
@addEventListener 'focusout', @focusedOut.bind(this)
@addEventListener 'blur', @blurred.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: @hasAttribute('mini')
placeholderText: @getAttribute('placeholder-text')
))
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
focusedOut: (event) ->
event.stopImmediatePropagation() if @contains(event.relatedTarget)
blurred: (event) ->
event.stopImmediatePropagation() if @contains(event.relatedTarget)
addGrammarScopeAttribute: ->
grammarScope = @model.getGrammar()?.scopeName?.replace(/\./g, ' ')
@setAttribute('data-grammar', grammarScope)
hasFocus: ->
this is document.activeElement or @contains(document.activeElement)
stopCommandEventPropagation = (commandListeners) ->
newCommandListeners = {}
for commandName, commandListener of commandListeners
do (commandListener) ->
newCommandListeners[commandName] = (event) ->
event.stopPropagation()
commandListener.call(this, event)
newCommandListeners
atom.commands.add '[is=atom-text-editor]', stopCommandEventPropagation(
'core:move-left': -> @getModel().moveLeft()
'core:move-right': -> @getModel().moveRight()
'core:select-left': -> @getModel().selectLeft()
'core:select-right': -> @getModel().selectRight()
'core:select-all': -> @getModel().selectAll()
'core:backspace': -> @getModel().backspace()
'core:delete': -> @getModel().delete()
'core:undo': -> @getModel().undo()
'core:redo': -> @getModel().redo()
'core:cut': -> @getModel().cutSelectedText()
'core:copy': -> @getModel().copySelectedText()
'core:paste': -> @getModel().pasteText()
'editor:move-to-previous-word': -> @getModel().moveToPreviousWord()
'editor:select-word': -> @getModel().selectWordsContainingCursors()
'editor:consolidate-selections': (event) -> event.abortKeyBinding() unless @getModel().consolidateSelections()
'editor:delete-to-beginning-of-word': -> @getModel().deleteToBeginningOfWord()
'editor:delete-to-beginning-of-line': -> @getModel().deleteToBeginningOfLine()
'editor:delete-to-end-of-line': -> @getModel().deleteToEndOfLine()
'editor:delete-to-end-of-word': -> @getModel().deleteToEndOfWord()
'editor:delete-line': -> @getModel().deleteLine()
'editor:cut-to-end-of-line': -> @getModel().cutToEndOfLine()
'editor:move-to-beginning-of-next-paragraph': -> @getModel().moveToBeginningOfNextParagraph()
'editor:move-to-beginning-of-previous-paragraph': -> @getModel().moveToBeginningOfPreviousParagraph()
'editor:move-to-beginning-of-screen-line': -> @getModel().moveToBeginningOfScreenLine()
'editor:move-to-beginning-of-line': -> @getModel().moveToBeginningOfLine()
'editor:move-to-end-of-screen-line': -> @getModel().moveToEndOfScreenLine()
'editor:move-to-end-of-line': -> @getModel().moveToEndOfLine()
'editor:move-to-first-character-of-line': -> @getModel().moveToFirstCharacterOfLine()
'editor:move-to-beginning-of-word': -> @getModel().moveToBeginningOfWord()
'editor:move-to-end-of-word': -> @getModel().moveToEndOfWord()
'editor:move-to-beginning-of-next-word': -> @getModel().moveToBeginningOfNextWord()
'editor:move-to-previous-word-boundary': -> @getModel().moveToPreviousWordBoundary()
'editor:move-to-next-word-boundary': -> @getModel().moveToNextWordBoundary()
'editor:select-to-beginning-of-next-paragraph': -> @getModel().selectToBeginningOfNextParagraph()
'editor:select-to-beginning-of-previous-paragraph': -> @getModel().selectToBeginningOfPreviousParagraph()
'editor:select-to-end-of-line': -> @getModel().selectToEndOfLine()
'editor:select-to-beginning-of-line': -> @getModel().selectToBeginningOfLine()
'editor:select-to-end-of-word': -> @getModel().selectToEndOfWord()
'editor:select-to-beginning-of-word': -> @getModel().selectToBeginningOfWord()
'editor:select-to-beginning-of-next-word': -> @getModel().selectToBeginningOfNextWord()
'editor:select-to-next-word-boundary': -> @getModel().selectToNextWordBoundary()
'editor:select-to-previous-word-boundary': -> @getModel().selectToPreviousWordBoundary()
'editor:select-to-first-character-of-line': -> @getModel().selectToFirstCharacterOfLine()
'editor:select-line': -> @getModel().selectLinesContainingCursors()
'editor:transpose': -> @getModel().transpose()
'editor:upper-case': -> @getModel().upperCase()
'editor:lower-case': -> @getModel().lowerCase()
)
atom.commands.add '[is=atom-text-editor]:not(.mini)', stopCommandEventPropagation(
'core:move-up': -> @getModel().moveUp()
'core:move-down': -> @getModel().moveDown()
'core:move-to-top': -> @getModel().moveToTop()
'core:move-to-bottom': -> @getModel().moveToBottom()
'core:page-up': -> @getModel().pageUp()
'core:page-down': -> @getModel().pageDown()
'core:select-up': -> @getModel().selectUp()
'core:select-down': -> @getModel().selectDown()
'core:select-to-top': -> @getModel().selectToTop()
'core:select-to-bottom': -> @getModel().selectToBottom()
'core:select-page-up': -> @getModel().selectPageUp()
'core:select-page-down': -> @getModel().selectPageDown()
'editor:indent': -> @getModel().indent()
'editor:auto-indent': -> @getModel().autoIndentSelectedRows()
'editor:indent-selected-rows': -> @getModel().indentSelectedRows()
'editor:outdent-selected-rows': -> @getModel().outdentSelectedRows()
'editor:newline': -> @getModel().insertNewline()
'editor:newline-below': -> @getModel().insertNewlineBelow()
'editor:newline-above': -> @getModel().insertNewlineAbove()
'editor:add-selection-below': -> @getModel().addSelectionBelow()
'editor:add-selection-above': -> @getModel().addSelectionAbove()
'editor:split-selections-into-lines': -> @getModel().splitSelectionsIntoLines()
'editor:toggle-soft-tabs': -> @getModel().toggleSoftTabs()
'editor:toggle-soft-wrap': -> @getModel().toggleSoftWrapped()
'editor:fold-all': -> @getModel().foldAll()
'editor:unfold-all': -> @getModel().unfoldAll()
'editor:fold-current-row': -> @getModel().foldCurrentRow()
'editor:unfold-current-row': -> @getModel().unfoldCurrentRow()
'editor:fold-selection': -> @getModel().foldSelectedLines()
'editor:fold-at-indent-level-1': -> @getModel().foldAllAtIndentLevel(0)
'editor:fold-at-indent-level-2': -> @getModel().foldAllAtIndentLevel(1)
'editor:fold-at-indent-level-3': -> @getModel().foldAllAtIndentLevel(2)
'editor:fold-at-indent-level-4': -> @getModel().foldAllAtIndentLevel(3)
'editor:fold-at-indent-level-5': -> @getModel().foldAllAtIndentLevel(4)
'editor:fold-at-indent-level-6': -> @getModel().foldAllAtIndentLevel(5)
'editor:fold-at-indent-level-7': -> @getModel().foldAllAtIndentLevel(6)
'editor:fold-at-indent-level-8': -> @getModel().foldAllAtIndentLevel(7)
'editor:fold-at-indent-level-9': -> @getModel().foldAllAtIndentLevel(8)
'editor:toggle-line-comments': -> @getModel().toggleLineCommentsInSelection()
'editor:log-cursor-scope': -> @getModel().logCursorScope()
'editor:checkout-head-revision': -> atom.project.getRepositories()[0]?.checkoutHeadForEditor(@getModel())
'editor:copy-path': -> @getModel().copyPathToClipboard()
'editor:move-line-up': -> @getModel().moveLineUp()
'editor:move-line-down': -> @getModel().moveLineDown()
'editor:duplicate-lines': -> @getModel().duplicateLines()
'editor:join-lines': -> @getModel().joinLines()
'editor:toggle-indent-guide': -> atom.config.set('editor.showIndentGuide', not atom.config.get('editor.showIndentGuide'))
'editor:toggle-line-numbers': -> atom.config.set('editor.showLineNumbers', not atom.config.get('editor.showLineNumbers'))
'editor:scroll-to-cursor': -> @getModel().scrollToCursorPosition()
)
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,48 @@ 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, attributes} = 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.setAttribute(name, value) for name, value of attributes if attributes?
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 +95,77 @@ 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
remove: (selector, keepData) ->
@model.destroy() unless keepData
super
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 +224,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 +259,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 +267,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 +302,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()
@ -504,6 +501,8 @@ class TextEditor extends Model
@mini = mini
@updateInvisibles()
isMini: -> @mini
# Set the number of characters that can be displayed horizontally in the
# editor.
#