Merge pull request #2497 from atom/cj-add-react-editor-shims

Add shims to the React View Editor
This commit is contained in:
Corey Johnson
2014-06-11 09:06:40 -07:00
14 changed files with 220 additions and 82 deletions

View File

@@ -72,9 +72,9 @@
"archive-view": "0.32.0",
"autocomplete": "0.28.0",
"autoflow": "0.17.0",
"autosave": "0.13.0",
"autosave": "0.14.0",
"background-tips": "0.14.0",
"bookmarks": "0.23.0",
"bookmarks": "0.24.0",
"bracket-matcher": "0.45.0",
"command-palette": "0.22.0",
"deprecation-cop": "0.6.0",
@@ -83,7 +83,7 @@
"feedback": "0.33.0",
"find-and-replace": "0.115.0",
"fuzzy-finder": "0.54.0",
"git-diff": "0.29.0",
"git-diff": "0.30.0",
"go-to-line": "0.22.0",
"grammar-selector": "0.27.0",
"image-view": "0.35.0",
@@ -96,7 +96,7 @@
"release-notes": "0.32.0",
"settings-view": "0.120.0",
"snippets": "0.45.0",
"spell-check": "0.36.0",
"spell-check": "0.37.0",
"status-bar": "0.40.0",
"styleguide": "0.29.0",
"symbols-view": "0.55.0",

View File

@@ -1072,8 +1072,6 @@ describe "EditorComponent", ->
expect(horizontalScrollbarNode.scrollWidth).toBe gutterNode.offsetWidth + editor.getScrollWidth()
describe "when a mousewheel event occurs on the editor", ->
describe "mousewheel events", ->
it "updates the scrollLeft or scrollTop on mousewheel events depending on which delta is greater (x or y)", ->
node.style.height = 4.5 * lineHeightInPixels + 'px'
@@ -1184,6 +1182,12 @@ describe "EditorComponent", ->
inputNode.dispatchEvent(new Event('input'))
expect(editor.lineForBufferRow(0)).toBe 'üvar quicksort = function () {'
it "does not handle input events when input is disabled", ->
component.setInputEnabled(false)
inputNode.value = 'x'
inputNode.dispatchEvent(new Event('input'))
expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {'
describe "commands", ->
describe "editor:consolidate-selections", ->
it "consolidates selections on the editor model, aborting the key binding if there is only one selection", ->

View File

@@ -10,6 +10,8 @@ describe "EditorView", ->
[buffer, editorView, editor, cachedEditor, cachedLineHeight, cachedCharWidth, fart] = []
beforeEach ->
atom.config.set 'core.useReactEditor', false
waitsForPromise ->
atom.workspace.open('sample.js').then (o) -> editor = o

View File

@@ -16,6 +16,7 @@ describe "PaneContainer", ->
containerA = new PaneContainer(root: pane1A)
pane2A = pane1A.splitRight(items: [new Item])
pane3A = pane2A.splitDown(items: [new Item])
pane3A.focus()
it "preserves the focused pane across serialization", ->
expect(pane3A.focused).toBe true

View File

@@ -439,10 +439,11 @@ describe "Pane", ->
expect(column.orientation).toBe 'vertical'
expect(column.children).toEqual [pane1, pane3, pane2]
it "sets up the new pane to be focused", ->
expect(pane1.focused).toBe false
it "activates the new pane", ->
expect(pane1.isActive()).toBe true
pane2 = pane1.splitRight()
expect(pane2.focused).toBe true
expect(pane1.isActive()).toBe false
expect(pane2.isActive()).toBe true
describe "::destroy()", ->
[container, pane1, pane2] = []

View File

@@ -199,6 +199,7 @@ describe "PaneView", ->
it "focuses the next pane", ->
container.attachToDom()
pane2.activate()
expect(pane.hasFocus()).toBe false
expect(pane2.hasFocus()).toBe true
pane2Model.destroy()

View File

@@ -89,7 +89,7 @@ beforeEach ->
config.set "editor.autoIndent", false
config.set "core.disabledPackages", ["package-that-throws-an-exception",
"package-with-broken-package-json", "package-with-broken-keymap"]
config.set "core.useReactEditor", false
config.set "core.useReactEditor", true
config.save.reset()
atom.config = config

View File

@@ -201,7 +201,9 @@ describe "WorkspaceView", ->
expect(rightEditorView.find(".line:first").text()).toBe " "
expect(leftEditorView.find(".line:first").text()).toBe " "
withInvisiblesShowing = "#{rightEditorView.invisibles.space}#{rightEditorView.invisibles.tab} #{rightEditorView.invisibles.space}#{rightEditorView.invisibles.eol}"
{invisibles} = rightEditorView.component.state
{space, tab, eol} = invisibles
withInvisiblesShowing = "#{space}#{tab} #{space}#{eol}"
atom.workspaceView.trigger "window:toggle-invisibles"
expect(rightEditorView.find(".line:first").text()).toBe withInvisiblesShowing

View File

@@ -230,8 +230,7 @@ class DisplayBuffer extends Model
@charWidthsByScope = {}
getScrollHeight: ->
unless @getLineHeightInPixels() > 0
throw new Error("You must assign lineHeightInPixels before calling ::getScrollHeight()")
return 0 unless @getLineHeightInPixels() > 0
@getLineCount() * @getLineHeightInPixels()
@@ -239,8 +238,7 @@ class DisplayBuffer extends Model
(@getMaxLineLength() * @getDefaultCharWidth()) + @getCursorWidth()
getVisibleRowRange: ->
unless @getLineHeightInPixels() > 0
throw new Error("You must assign a non-zero lineHeightInPixels before calling ::getVisibleRowRange()")
return [0, 0] unless @getLineHeightInPixels() > 0
heightInLines = Math.ceil(@getHeight() / @getLineHeightInPixels()) + 1
startRow = Math.floor(@getScrollTop() / @getLineHeightInPixels())

View File

@@ -37,6 +37,7 @@ EditorComponent = React.createClass
overflowChangedEventsPaused: false
overflowChangedWhilePaused: false
measureLineHeightAndDefaultCharWidthWhenShown: false
inputEnabled: true
render: ->
{focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, visible} = @state
@@ -165,7 +166,9 @@ EditorComponent = React.createClass
window.removeEventListener('resize', @onWindowResize)
componentWillUpdate: ->
@props.parentView.trigger 'cursor:moved' if @cursorsMoved
if @props.editor.isAlive()
@props.parentView.trigger 'cursor:moved' if @cursorsMoved
@props.parentView.trigger 'selection:changed' if @selectionChanged
componentDidUpdate: (prevProps, prevState) ->
@pendingChanges.length = 0
@@ -457,6 +460,8 @@ EditorComponent = React.createClass
scrollViewNode.scrollLeft = 0
onInput: (char, replaceLastCharacter) ->
return unless @inputEnabled
{editor} = @props
if replaceLastCharacter
@@ -531,6 +536,7 @@ EditorComponent = React.createClass
onCursorsMoved: ->
@cursorsMoved = true
@requestUpdate()
onDecorationChanged: ->
@decorationChangedImmediate ?= setImmediate =>
@@ -707,50 +713,21 @@ EditorComponent = React.createClass
show: ->
@setState(visible: true)
runScrollBenchmark: ->
unless process.env.NODE_ENV is 'production'
ReactPerf = require 'react-atom-fork/lib/ReactDefaultPerf'
ReactPerf.start()
node = @getDOMNode()
scroll = (delta, done) ->
dispatchMouseWheelEvent = ->
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -0, wheelDeltaY: -delta))
stopScrolling = ->
clearInterval(interval)
done?()
interval = setInterval(dispatchMouseWheelEvent, 10)
setTimeout(stopScrolling, 500)
console.timeline('scroll')
scroll 50, ->
scroll 100, ->
scroll 200, ->
scroll 400, ->
scroll 800, ->
scroll 1600, ->
console.timelineEnd('scroll')
unless process.env.NODE_ENV is 'production'
ReactPerf.stop()
console.log "Inclusive"
ReactPerf.printInclusive()
console.log "Exclusive"
ReactPerf.printExclusive()
console.log "Wasted"
ReactPerf.printWasted()
getFontSize: ->
@state.fontSize
setFontSize: (fontSize) ->
@setState({fontSize})
setLineHeight: (lineHeight) ->
@setState({lineHeight})
getFontFamily: ->
@state.fontFamily
setFontFamily: (fontFamily) ->
@setState({fontFamily})
setLineHeight: (lineHeight) ->
@setState({lineHeight})
setShowIndentGuide: (showIndentGuide) ->
@setState({showIndentGuide})
@@ -786,6 +763,48 @@ EditorComponent = React.createClass
left = clientX - scrollViewClientRect.left + editor.getScrollLeft()
{top, left}
getModel: ->
@props.editor
isInputEnabled: -> @inputEnabled
setInputEnabled: (@inputEnabled) -> @inputEnabled
updateParentViewFocusedClassIfNeeded: (prevState) ->
if prevState.focused isnt @state.focused
@props.parentView.toggleClass('is-focused', @props.focused)
runScrollBenchmark: ->
unless process.env.NODE_ENV is 'production'
ReactPerf = require 'react-atom-fork/lib/ReactDefaultPerf'
ReactPerf.start()
node = @getDOMNode()
scroll = (delta, done) ->
dispatchMouseWheelEvent = ->
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -0, wheelDeltaY: -delta))
stopScrolling = ->
clearInterval(interval)
done?()
interval = setInterval(dispatchMouseWheelEvent, 10)
setTimeout(stopScrolling, 500)
console.timeline('scroll')
scroll 50, ->
scroll 100, ->
scroll 200, ->
scroll 400, ->
scroll 800, ->
scroll 1600, ->
console.timelineEnd('scroll')
unless process.env.NODE_ENV is 'production'
ReactPerf.stop()
console.log "Inclusive"
ReactPerf.printInclusive()
console.log "Exclusive"
ReactPerf.printExclusive()
console.log "Wasted"
ReactPerf.printWasted()

View File

@@ -282,7 +282,7 @@ class EditorView extends View
@editor.moveCursorUp(@getPageRows())
@scrollTop(newScrollTop, adjustVerticalScrollbar: true)
# Public: Gets the number of actual page rows existing in an editor.
# Gets the number of actual page rows existing in an editor.
#
# Returns a {Number}.
getPageRows: ->

View File

@@ -62,7 +62,7 @@ GutterComponent = React.createClass
# since the real line numbers are absolutely positioned for performance reasons.
appendDummyLineNumber: ->
{maxLineNumberDigits} = @props
WrapperDiv.innerHTML = @buildLineNumberHTML(0, false, maxLineNumberDigits)
WrapperDiv.innerHTML = @buildLineNumberHTML(-1, false, maxLineNumberDigits)
@dummyLineNumberNode = WrapperDiv.children[0]
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
@@ -140,7 +140,7 @@ GutterComponent = React.createClass
if decorations?
for decoration in decorations
classes += decoration.class + ' ' if not softWrapped or softWrapped and decoration.softWrap
classes += 'line-number'
classes += "line-number line-number-#{bufferRow}"
"<div class=\"#{classes}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"

View File

@@ -212,7 +212,10 @@ class Pane extends Model
@destroyItem(item) for item in @getItems() when item isnt @activeItem
destroy: ->
super unless @container?.isAlive() and @container?.getPanes().length is 1
if @container?.isAlive() and @container.getPanes().length is 1
@destroyItems()
else
super
# Called by model superclass.
destroyed: ->
@@ -331,7 +334,7 @@ class Pane extends Model
if @parent.orientation isnt orientation
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
newPane = new @constructor(extend({focused: true}, params))
newPane = new @constructor(params)
switch side
when 'before' then @parent.insertChildBefore(this, newPane)
when 'after' then @parent.insertChildAfter(this, newPane)

View File

@@ -23,6 +23,41 @@ class ReactEditorView extends View
Object.defineProperty @::, 'charWidth', get: -> @editor.getDefaultCharWidth()
Object.defineProperty @::, 'firstRenderedScreenRow', get: -> @component.getRenderedRowRange()[0]
Object.defineProperty @::, 'lastRenderedScreenRow', get: -> @component.getRenderedRowRange()[1]
Object.defineProperty @::, 'active', get: -> @is(@getPane().activeView)
Object.defineProperty @::, 'isFocused', get: -> @component?.state.focused
afterAttach: (onDom) ->
return unless onDom
return if @attached
@attached = true
props = defaults({@editor, parentView: this}, @props)
@component = React.renderComponent(EditorComponent(props), @element)
node = @component.getDOMNode()
@scrollView = $(node).find('.scroll-view')
@underlayer = $(node).find('.selections').addClass('underlayer')
@overlayer = $(node).find('.lines').addClass('overlayer')
@hiddenInput = $(node).find('.hidden-input')
@gutter = $(node).find('.gutter')
@gutter.removeClassFromAllLines = (klass) =>
Grim.deprecate 'You no longer need to manually add and remove classes. Use `Editor::removeDecorationFromBufferRow()` and related functions'
@gutter.find('.line-number').removeClass(klass)
@gutter.getLineNumberElement = (bufferRow) =>
@gutter.find("[data-buffer-row='#{bufferRow}']")
@gutter.addClassToLine = (bufferRow, klass) =>
Grim.deprecate 'You no longer need to manually add and remove classes. Use `Editor::addDecorationToBufferRow()` and related functions'
lines = @gutter.find("[data-buffer-row='#{bufferRow}']")
lines.addClass(klass)
lines.length > 0
@focus() if @focusOnAttach
@trigger 'editor:attached', [this]
scrollTop: (scrollTop) ->
if scrollTop?
@@ -36,36 +71,21 @@ class ReactEditorView extends View
else
@editor.getScrollLeft()
scrollToBottom: ->
@editor.setScrollBottom(Infinity)
scrollToScreenPosition: (screenPosition) ->
@editor.scrollToScreenPosition(screenPosition)
scrollToBufferPosition: (bufferPosition) ->
@editor.scrollToBufferPosition(bufferPosition)
afterAttach: (onDom) ->
return unless onDom
@attached = true
props = defaults({@editor, parentView: this}, @props)
@component = React.renderComponent(EditorComponent(props), @element)
scrollToCursorPosition: ->
@editor.scrollToCursorPosition()
node = @component.getDOMNode()
@underlayer = $(node).find('.selections')
@gutter = $(node).find('.gutter')
@gutter.removeClassFromAllLines = (klass) =>
Grim.deprecate 'You no longer need to manually add and remove classes. Use `Editor::removeDecorationFromBufferRow()` and related functions'
@gutter.find('.line-number').removeClass(klass)
@gutter.addClassToLine = (bufferRow, klass) =>
Grim.deprecate 'You no longer need to manually add and remove classes. Use `Editor::addDecorationToBufferRow()` and related functions'
lines = @gutter.find("[data-buffer-row='#{bufferRow}']")
lines.addClass(klass)
lines.length > 0
@focus() if @focusOnAttach
@trigger 'editor:attached', [this]
scrollToPixelPosition: (pixelPosition) ->
screenPosition = screenPositionForPixelPosition(pixelPosition)
@editor.scrollToScreenPosition(screenPosition)
pixelPositionForBufferPosition: (bufferPosition) ->
@editor.pixelPositionForBufferPosition(bufferPosition)
@@ -83,6 +103,26 @@ class ReactEditorView extends View
@attached = false
@trigger 'editor:detached', this
# Public: Split the editor view left.
splitLeft: ->
pane = @getPane()
pane?.splitLeft(pane?.copyActiveItem()).activeView
# Public: Split the editor view right.
splitRight: ->
pane = @getPane()
pane?.splitRight(pane?.copyActiveItem()).activeView
# Public: Split the editor view up.
splitUp: ->
pane = @getPane()
pane?.splitUp(pane?.copyActiveItem()).activeView
# Public: Split the editor view down.
splitDown: ->
pane = @getPane()
pane?.splitDown(pane?.copyActiveItem()).activeView
getPane: ->
@closest('.pane').view()
@@ -94,10 +134,77 @@ class ReactEditorView extends View
hide: ->
super
@component.hide()
@component?.hide()
show: ->
super
@component.show()
@component?.show()
pageDown: ->
@editor.pageDown()
pageUp: ->
@editor.pageUp()
getModel: ->
@component?.getModel()
getFirstVisibleScreenRow: ->
@editor.getVisibleRowRange()[0]
getLastVisibleScreenRow: ->
@editor.getVisibleRowRange()[1]
getFontFamily: ->
@component?.getFontFamily()
setFontFamily: (fontFamily)->
@component?.setFontFamily(fontFamily)
getFontSize: ->
@component?.getFontSize()
setFontSize: (fontSize)->
@component?.setFontSize(fontSize)
setWidthInChars: (widthInChars) ->
@component.getDOMNode().style.width = (@editor.getDefaultCharWidth() * widthInChars) + 'px'
setLineHeight: (lineHeight) ->
@component.setLineHeight(lineHeight)
setShowIndentGuide: (showIndentGuide) ->
@component.setShowIndentGuide(showIndentGuide)
setSoftWrap: (softWrap) ->
@editor.setSoftWrap(softWrap)
setShowInvisibles: (showInvisibles) ->
@component.setShowInvisibles(showInvisibles)
toggleSoftWrap: ->
@editor.toggleSoftWrap()
toggleSoftTabs: ->
@editor.toggleSoftTabs()
getText: ->
@editor.getText()
setText: (text) ->
@editor.setText(text)
insertText: (text) ->
@editor.insertText(text)
isInputEnabled: ->
@component.isInputEnabled()
setInputEnabled: (inputEnabled) ->
@component.setInputEnabled(inputEnabled)
requestDisplayUpdate: -> # No-op shim for find-and-replace
updateDisplay: -> # No-op shim for package specs
redraw: -> # No-op shim