mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-21 07:58:04 +03:00
Merge branch 'ns-manual-dom-updates'
This commit is contained in:
commit
964809373b
@ -82,6 +82,7 @@ beforeEach ->
|
||||
atom.keymaps.keyBindings = _.clone(keyBindingsToRestore)
|
||||
atom.commands.restoreSnapshot(commandsToRestore)
|
||||
atom.styles.restoreSnapshot(styleElementsToRestore)
|
||||
atom.views.clearDocumentRequests()
|
||||
|
||||
atom.workspaceViewParentSelector = '#jasmine-content'
|
||||
|
||||
|
@ -46,7 +46,7 @@ describe "TextEditorComponent", ->
|
||||
|
||||
lineHeightInPixels = editor.getLineHeightInPixels()
|
||||
charWidth = editor.getDefaultCharWidth()
|
||||
componentNode = component.getDOMNode()
|
||||
componentNode = component.domNode
|
||||
verticalScrollbarNode = componentNode.querySelector('.vertical-scrollbar')
|
||||
horizontalScrollbarNode = componentNode.querySelector('.horizontal-scrollbar')
|
||||
|
||||
@ -193,7 +193,8 @@ describe "TextEditorComponent", ->
|
||||
expect(linesNode.style.backgroundColor).toBe backgroundColor
|
||||
|
||||
wrapperNode.style.backgroundColor = 'rgb(255, 0, 0)'
|
||||
advanceClock(component.domPollingInterval)
|
||||
|
||||
advanceClock(atom.views.documentPollingInterval)
|
||||
nextAnimationFrame()
|
||||
expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
|
||||
|
||||
@ -466,11 +467,6 @@ describe "TextEditorComponent", ->
|
||||
expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy()
|
||||
|
||||
describe "gutter rendering", ->
|
||||
[gutter] = []
|
||||
|
||||
beforeEach ->
|
||||
{gutter} = component.refs
|
||||
|
||||
it "renders the currently-visible line numbers", ->
|
||||
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
@ -567,32 +563,32 @@ describe "TextEditorComponent", ->
|
||||
|
||||
# favor gutter color if it's assigned
|
||||
gutterNode.style.backgroundColor = 'rgb(255, 0, 0)'
|
||||
advanceClock(component.domPollingInterval)
|
||||
advanceClock(atom.views.documentPollingInterval)
|
||||
nextAnimationFrame()
|
||||
expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
|
||||
|
||||
it "hides or shows the gutter based on the '::isGutterVisible' property on the model and the global 'editor.showLineNumbers' config setting", ->
|
||||
expect(component.refs.gutter?).toBe true
|
||||
expect(component.gutterComponent?).toBe true
|
||||
|
||||
editor.setGutterVisible(false)
|
||||
nextAnimationFrame()
|
||||
|
||||
expect(component.refs.gutter?).toBe false
|
||||
expect(componentNode.querySelector('.gutter')).toBeNull()
|
||||
|
||||
atom.config.set("editor.showLineNumbers", false)
|
||||
expect(nextAnimationFrame).toBe noAnimationFrame
|
||||
nextAnimationFrame()
|
||||
|
||||
expect(component.refs.gutter?).toBe false
|
||||
expect(componentNode.querySelector('.gutter')).toBeNull()
|
||||
|
||||
editor.setGutterVisible(true)
|
||||
expect(nextAnimationFrame).toBe noAnimationFrame
|
||||
nextAnimationFrame()
|
||||
|
||||
expect(component.refs.gutter?).toBe false
|
||||
expect(componentNode.querySelector('.gutter')).toBeNull()
|
||||
|
||||
atom.config.set("editor.showLineNumbers", true)
|
||||
nextAnimationFrame()
|
||||
|
||||
expect(component.refs.gutter?).toBe true
|
||||
expect(componentNode.querySelector('.gutter')).toBeDefined()
|
||||
expect(component.lineNumberNodeForScreenRow(3)?).toBe true
|
||||
|
||||
describe "fold decorations", ->
|
||||
@ -706,13 +702,13 @@ describe "TextEditorComponent", ->
|
||||
|
||||
cursorNodes = componentNode.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 2
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
|
||||
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)"
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)"
|
||||
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
|
||||
|
||||
wrapperView.on 'cursor:moved', cursorMovedListener = jasmine.createSpy('cursorMovedListener')
|
||||
cursor3.setScreenPosition([4, 11], autoscroll: false)
|
||||
nextAnimationFrame()
|
||||
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)"
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)"
|
||||
expect(cursorMovedListener).toHaveBeenCalled()
|
||||
|
||||
cursor3.destroy()
|
||||
@ -800,11 +796,11 @@ describe "TextEditorComponent", ->
|
||||
|
||||
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
||||
|
||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||
advanceClock(component.cursorBlinkPeriod / 2)
|
||||
nextAnimationFrame()
|
||||
expect(cursorsNode.classList.contains('blink-off')).toBe true
|
||||
|
||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||
advanceClock(component.cursorBlinkPeriod / 2)
|
||||
nextAnimationFrame()
|
||||
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
||||
|
||||
@ -813,8 +809,8 @@ describe "TextEditorComponent", ->
|
||||
nextAnimationFrame()
|
||||
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
||||
|
||||
advanceClock(component.props.cursorBlinkResumeDelay)
|
||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||
advanceClock(component.cursorBlinkResumeDelay)
|
||||
advanceClock(component.cursorBlinkPeriod / 2)
|
||||
nextAnimationFrame()
|
||||
expect(cursorsNode.classList.contains('blink-off')).toBe true
|
||||
|
||||
@ -1501,12 +1497,14 @@ describe "TextEditorComponent", ->
|
||||
expect(inputNode.offsetLeft).toBe 0
|
||||
|
||||
# In bounds and focused
|
||||
inputNode.focus() # updates via state change
|
||||
wrapperNode.focus() # updates via state change
|
||||
nextAnimationFrame()
|
||||
expect(inputNode.offsetTop).toBe (5 * lineHeightInPixels) - editor.getScrollTop()
|
||||
expect(inputNode.offsetLeft).toBe (4 * charWidth) - editor.getScrollLeft()
|
||||
|
||||
# In bounds, not focused
|
||||
inputNode.blur() # updates via state change
|
||||
nextAnimationFrame()
|
||||
expect(inputNode.offsetTop).toBe 0
|
||||
expect(inputNode.offsetLeft).toBe 0
|
||||
|
||||
@ -1518,6 +1516,7 @@ describe "TextEditorComponent", ->
|
||||
|
||||
# Out of bounds, focused
|
||||
inputNode.focus() # updates via state change
|
||||
nextAnimationFrame()
|
||||
expect(inputNode.offsetTop).toBe 0
|
||||
expect(inputNode.offsetLeft).toBe 0
|
||||
|
||||
@ -1839,9 +1838,11 @@ describe "TextEditorComponent", ->
|
||||
it "adds the 'is-focused' class to the editor when the hidden input is focused", ->
|
||||
expect(document.activeElement).toBe document.body
|
||||
inputNode.focus()
|
||||
nextAnimationFrame()
|
||||
expect(componentNode.classList.contains('is-focused')).toBe true
|
||||
expect(wrapperView.hasClass('is-focused')).toBe true
|
||||
inputNode.blur()
|
||||
nextAnimationFrame()
|
||||
expect(componentNode.classList.contains('is-focused')).toBe false
|
||||
expect(wrapperView.hasClass('is-focused')).toBe false
|
||||
|
||||
@ -2351,11 +2352,11 @@ describe "TextEditorComponent", ->
|
||||
wrapperView.appendTo(hiddenParent)
|
||||
|
||||
{component} = wrapperView
|
||||
componentNode = component.getDOMNode()
|
||||
componentNode = component.domNode
|
||||
expect(componentNode.querySelectorAll('.line').length).toBe 0
|
||||
|
||||
hiddenParent.style.display = 'block'
|
||||
advanceClock(component.domPollingInterval)
|
||||
advanceClock(atom.views.documentPollingInterval)
|
||||
|
||||
expect(componentNode.querySelectorAll('.line').length).toBeGreaterThan 0
|
||||
|
||||
@ -2465,14 +2466,15 @@ describe "TextEditorComponent", ->
|
||||
expect(parseInt(newHeight)).toBeLessThan wrapperNode.offsetHeight
|
||||
wrapperNode.style.height = newHeight
|
||||
|
||||
advanceClock(component.domPollingInterval)
|
||||
advanceClock(atom.views.documentPollingInterval)
|
||||
nextAnimationFrame()
|
||||
expect(componentNode.querySelectorAll('.line')).toHaveLength(4 + lineOverdrawMargin + 1)
|
||||
|
||||
gutterWidth = componentNode.querySelector('.gutter').offsetWidth
|
||||
componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
|
||||
advanceClock(component.domPollingInterval)
|
||||
nextAnimationFrame()
|
||||
advanceClock(atom.views.documentPollingInterval)
|
||||
nextAnimationFrame() # won't poll until cursor blinks
|
||||
nextAnimationFrame() # handle update requested by poll
|
||||
expect(componentNode.querySelector('.line').textContent).toBe "var quicksort "
|
||||
|
||||
it "accounts for the scroll view's padding when determining the wrap location", ->
|
||||
@ -2480,7 +2482,7 @@ describe "TextEditorComponent", ->
|
||||
scrollViewNode.style.paddingLeft = 20 + 'px'
|
||||
componentNode.style.width = 30 * charWidth + 'px'
|
||||
|
||||
advanceClock(component.domPollingInterval)
|
||||
advanceClock(atom.views.documentPollingInterval)
|
||||
nextAnimationFrame()
|
||||
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe "var quicksort = "
|
||||
@ -2558,7 +2560,7 @@ describe "TextEditorComponent", ->
|
||||
expect(wrapperNode.classList.contains('mini')).toBe true
|
||||
|
||||
it "does not have an opaque background on lines", ->
|
||||
expect(component.refs.lines.getDOMNode().getAttribute('style')).not.toContain 'background-color'
|
||||
expect(component.linesComponent.domNode.getAttribute('style')).not.toContain 'background-color'
|
||||
|
||||
it "does not render invisible characters", ->
|
||||
atom.config.set('editor.invisibles', eol: 'E')
|
||||
|
@ -46,12 +46,12 @@ describe "TextEditorElement", ->
|
||||
jasmine.attachToDOM(element)
|
||||
|
||||
component = element.component
|
||||
expect(component.isMounted()).toBe true
|
||||
expect(component.mounted).toBe true
|
||||
element.remove()
|
||||
expect(component.isMounted()).toBe false
|
||||
expect(component.mounted).toBe false
|
||||
|
||||
jasmine.attachToDOM(element)
|
||||
expect(element.component.isMounted()).toBe true
|
||||
expect(element.component.mounted).toBe true
|
||||
|
||||
describe "when the editor.useShadowDOM config option is false", ->
|
||||
it "mounts the react component and unmounts when removed from the dom", ->
|
||||
@ -61,9 +61,9 @@ describe "TextEditorElement", ->
|
||||
jasmine.attachToDOM(element)
|
||||
|
||||
component = element.component
|
||||
expect(component.isMounted()).toBe true
|
||||
expect(component.mounted).toBe true
|
||||
element.getModel().destroy()
|
||||
expect(component.isMounted()).toBe false
|
||||
expect(component.mounted).toBe false
|
||||
|
||||
describe "focus and blur handling", ->
|
||||
describe "when the editor.useShadowDOM config option is true", ->
|
||||
|
@ -323,6 +323,69 @@ describe "TextEditorPresenter", ->
|
||||
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
|
||||
expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.contentHeight - presenter.clientHeight
|
||||
|
||||
describe ".hiddenInput", ->
|
||||
describe ".top/.left", ->
|
||||
it "is positioned over the last cursor it is in view and the editor is focused", ->
|
||||
editor.setCursorBufferPosition([3, 6])
|
||||
presenter = buildPresenter(focused: false, explicitHeight: 50, contentFrameWidth: 300, horizontalScrollbarHeight: 0, verticalScrollbarWidth: 0)
|
||||
expectValues presenter.state.hiddenInput, {top: 0, left: 0}
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setFocused(true)
|
||||
expectValues presenter.state.hiddenInput, {top: 3 * 10, left: 6 * 10}
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(15)
|
||||
expectValues presenter.state.hiddenInput, {top: (3 * 10) - 15, left: 6 * 10}
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setScrollLeft(35)
|
||||
expectValues presenter.state.hiddenInput, {top: (3 * 10) - 15, left: (6 * 10) - 35}
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(40)
|
||||
expectValues presenter.state.hiddenInput, {top: 0, left: (6 * 10) - 35}
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setScrollLeft(70)
|
||||
expectValues presenter.state.hiddenInput, {top: 0, left: 0}
|
||||
|
||||
expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43])
|
||||
expectValues presenter.state.hiddenInput, {top: 50 - 10, left: 300 - 10}
|
||||
|
||||
newCursor = null
|
||||
expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10])
|
||||
expectValues presenter.state.hiddenInput, {top: (6 * 10) - 40, left: (10 * 10) - 70}
|
||||
|
||||
expectStateUpdate presenter, -> newCursor.destroy()
|
||||
expectValues presenter.state.hiddenInput, {top: 50 - 10, left: 300 - 10}
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setFocused(false)
|
||||
expectValues presenter.state.hiddenInput, {top: 0, left: 0}
|
||||
|
||||
describe ".height", ->
|
||||
it "is assigned based on the line height", ->
|
||||
presenter = buildPresenter()
|
||||
expect(presenter.state.hiddenInput.height).toBe 10
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setLineHeight(20)
|
||||
expect(presenter.state.hiddenInput.height).toBe 20
|
||||
|
||||
describe ".width", ->
|
||||
it "is assigned based on the width of the character following the cursor", ->
|
||||
waitsForPromise -> atom.packages.activatePackage('language-javascript')
|
||||
|
||||
runs ->
|
||||
editor.setCursorBufferPosition([3, 6])
|
||||
presenter = buildPresenter()
|
||||
expect(presenter.state.hiddenInput.width).toBe 10
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15)
|
||||
expect(presenter.state.hiddenInput.width).toBe 15
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
|
||||
expect(presenter.state.hiddenInput.width).toBe 20
|
||||
|
||||
it "is 2px at the end of lines", ->
|
||||
presenter = buildPresenter()
|
||||
editor.setCursorBufferPosition([3, Infinity])
|
||||
expect(presenter.state.hiddenInput.width).toBe 2
|
||||
|
||||
describe ".content", ->
|
||||
describe ".scrollingVertically", ->
|
||||
it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", ->
|
||||
@ -1907,6 +1970,42 @@ describe "TextEditorPresenter", ->
|
||||
editor.undo()
|
||||
expect(lineNumberStateForScreenRow(presenter, 11).foldable).toBe false
|
||||
|
||||
describe ".visible", ->
|
||||
it "is true iff the editor isn't mini, ::isGutterVisible is true on the editor, and 'editor.showLineNumbers' is enabled in config", ->
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(editor.isGutterVisible()).toBe true
|
||||
expect(presenter.state.gutter.visible).toBe true
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(true)
|
||||
expect(presenter.state.gutter.visible).toBe false
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(false)
|
||||
expect(presenter.state.gutter.visible).toBe true
|
||||
|
||||
expectStateUpdate presenter, -> editor.setGutterVisible(false)
|
||||
expect(presenter.state.gutter.visible).toBe false
|
||||
|
||||
expectStateUpdate presenter, -> editor.setGutterVisible(true)
|
||||
expect(presenter.state.gutter.visible).toBe true
|
||||
|
||||
expectStateUpdate presenter, -> atom.config.set('editor.showLineNumbers', false)
|
||||
expect(presenter.state.gutter.visible).toBe false
|
||||
|
||||
it "updates when the editor's grammar changes", ->
|
||||
presenter = buildPresenter()
|
||||
|
||||
atom.config.set('editor.showLineNumbers', false, scopeSelector: '.source.js')
|
||||
expect(presenter.state.gutter.visible).toBe true
|
||||
stateUpdated = false
|
||||
presenter.onDidUpdateState -> stateUpdated = true
|
||||
|
||||
waitsForPromise -> atom.packages.activatePackage('language-javascript')
|
||||
|
||||
runs ->
|
||||
expect(stateUpdated).toBe true
|
||||
expect(presenter.state.gutter.visible).toBe false
|
||||
|
||||
describe ".height", ->
|
||||
it "tracks the computed content height if ::autoHeight is true so the editor auto-expands vertically", ->
|
||||
presenter = buildPresenter(explicitHeight: null, autoHeight: true)
|
||||
@ -1924,6 +2023,15 @@ describe "TextEditorPresenter", ->
|
||||
expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n")
|
||||
expect(presenter.state.height).toBe editor.getScreenLineCount() * 20
|
||||
|
||||
describe ".focused", ->
|
||||
it "tracks the value of ::focused", ->
|
||||
presenter = buildPresenter(focused: false)
|
||||
expect(presenter.state.focused).toBe false
|
||||
expectStateUpdate presenter, -> presenter.setFocused(true)
|
||||
expect(presenter.state.focused).toBe true
|
||||
expectStateUpdate presenter, -> presenter.setFocused(false)
|
||||
expect(presenter.state.focused).toBe false
|
||||
|
||||
# disabled until we fix an issue with display buffer markers not updating when
|
||||
# they are moved on screen but not in the buffer
|
||||
xdescribe "when the model and view measurements are mutated randomly", ->
|
||||
|
@ -85,3 +85,78 @@ describe "ViewRegistry", ->
|
||||
expect(registry.getView(new TestModel) instanceof TestView).toBe true
|
||||
disposable.dispose()
|
||||
expect(-> registry.getView(new TestModel)).toThrow()
|
||||
|
||||
describe "::updateDocument(fn) and ::readDocument(fn)", ->
|
||||
frameRequests = null
|
||||
|
||||
beforeEach ->
|
||||
frameRequests = []
|
||||
spyOn(window, 'requestAnimationFrame').andCallFake (fn) -> frameRequests.push(fn)
|
||||
|
||||
it "performs all pending writes before all pending reads on the next animation frame", ->
|
||||
events = []
|
||||
|
||||
registry.updateDocument -> events.push('write 1')
|
||||
registry.readDocument -> events.push('read 1')
|
||||
registry.readDocument -> events.push('read 2')
|
||||
registry.updateDocument -> events.push('write 2')
|
||||
|
||||
expect(events).toEqual []
|
||||
|
||||
expect(frameRequests.length).toBe 1
|
||||
frameRequests[0]()
|
||||
expect(events).toEqual ['write 1', 'write 2', 'read 1', 'read 2']
|
||||
|
||||
frameRequests = []
|
||||
events = []
|
||||
disposable = registry.updateDocument -> events.push('write 3')
|
||||
registry.updateDocument -> events.push('write 4')
|
||||
registry.readDocument -> events.push('read 3')
|
||||
|
||||
disposable.dispose()
|
||||
|
||||
expect(frameRequests.length).toBe 1
|
||||
frameRequests[0]()
|
||||
expect(events).toEqual ['write 4', 'read 3']
|
||||
|
||||
it "pauses DOM polling when reads or writes are pending", ->
|
||||
spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
|
||||
spyOn(window, 'clearInterval').andCallFake(fakeClearInterval)
|
||||
events = []
|
||||
|
||||
registry.pollDocument -> events.push('poll')
|
||||
registry.updateDocument -> events.push('write')
|
||||
registry.readDocument -> events.push('read')
|
||||
|
||||
advanceClock(registry.documentPollingInterval)
|
||||
expect(events).toEqual []
|
||||
|
||||
frameRequests[0]()
|
||||
expect(events).toEqual ['write', 'read', 'poll']
|
||||
|
||||
advanceClock(registry.documentPollingInterval)
|
||||
expect(events).toEqual ['write', 'read', 'poll', 'poll']
|
||||
|
||||
describe "::pollDocument(fn)", ->
|
||||
it "calls all registered reader functions on an interval until they are disabled via a returned disposable", ->
|
||||
spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
|
||||
|
||||
events = []
|
||||
disposable1 = registry.pollDocument -> events.push('poll 1')
|
||||
disposable2 = registry.pollDocument -> events.push('poll 2')
|
||||
|
||||
expect(events).toEqual []
|
||||
|
||||
advanceClock(registry.documentPollingInterval)
|
||||
expect(events).toEqual ['poll 1', 'poll 2']
|
||||
|
||||
advanceClock(registry.documentPollingInterval)
|
||||
expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2']
|
||||
|
||||
disposable1.dispose()
|
||||
advanceClock(registry.documentPollingInterval)
|
||||
expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2']
|
||||
|
||||
disposable2.dispose()
|
||||
advanceClock(registry.documentPollingInterval)
|
||||
expect(events).toEqual ['poll 1', 'poll 2', 'poll 1', 'poll 2', 'poll 2']
|
||||
|
@ -162,10 +162,6 @@ module.exports =
|
||||
default: 300
|
||||
minimum: 0
|
||||
description: 'Time interval in milliseconds within which operations will be grouped together in the undo history'
|
||||
useHardwareAcceleration:
|
||||
type: 'boolean'
|
||||
default: true
|
||||
description: 'Disabling will improve editor font rendering but reduce scrolling performance.'
|
||||
useShadowDOM:
|
||||
type: 'boolean'
|
||||
default: true
|
||||
|
@ -1,14 +0,0 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqualForProperties} = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
CursorComponent = React.createClass
|
||||
displayName: 'CursorComponent'
|
||||
|
||||
render: ->
|
||||
{pixelRect} = @props
|
||||
{top, left, height, width} = pixelRect
|
||||
WebkitTransform = "translate(#{left}px, #{top}px)"
|
||||
|
||||
div className: 'cursor', style: {height, width, WebkitTransform}
|
@ -1,19 +1,54 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{debounce, toArray, isEqualForProperties, isEqual} = require 'underscore-plus'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
CursorComponent = require './cursor-component'
|
||||
|
||||
module.exports =
|
||||
CursorsComponent = React.createClass
|
||||
displayName: 'CursorsComponent'
|
||||
class CursorsComponent
|
||||
oldState: null
|
||||
|
||||
render: ->
|
||||
{presenter} = @props
|
||||
constructor: (@presenter) ->
|
||||
@cursorNodesById = {}
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.classList.add('cursors')
|
||||
@updateSync()
|
||||
|
||||
className = 'cursors'
|
||||
className += ' blink-off' if presenter.state.content.blinkCursorsOff
|
||||
updateSync: ->
|
||||
newState = @presenter.state.content
|
||||
@oldState ?= {cursors: {}}
|
||||
|
||||
div {className},
|
||||
for key, pixelRect of presenter.state.content.cursors
|
||||
CursorComponent({key, pixelRect})
|
||||
# update blink class
|
||||
if newState.blinkCursorsOff isnt @oldState.blinkCursorsOff
|
||||
if newState.blinkCursorsOff
|
||||
@domNode.classList.add 'blink-off'
|
||||
else
|
||||
@domNode.classList.remove 'blink-off'
|
||||
@oldState.blinkCursorsOff = newState.blinkCursorsOff
|
||||
|
||||
# remove cursors
|
||||
for id of @oldState.cursors
|
||||
unless newState.cursors[id]?
|
||||
@cursorNodesById[id].remove()
|
||||
delete @cursorNodesById[id]
|
||||
delete @oldState.cursors[id]
|
||||
|
||||
# add or update cursors
|
||||
for id, cursorState of newState.cursors
|
||||
unless @oldState.cursors[id]?
|
||||
cursorNode = document.createElement('div')
|
||||
cursorNode.classList.add('cursor')
|
||||
@cursorNodesById[id] = cursorNode
|
||||
@domNode.appendChild(cursorNode)
|
||||
@updateCursorNode(id, cursorState)
|
||||
|
||||
updateCursorNode: (id, newCursorState) ->
|
||||
cursorNode = @cursorNodesById[id]
|
||||
oldCursorState = (@oldState.cursors[id] ?= {})
|
||||
|
||||
if newCursorState.top isnt oldCursorState.top or newCursorState.left isnt oldCursorState.left
|
||||
cursorNode.style['-webkit-transform'] = "translate(#{newCursorState.left}px, #{newCursorState.top}px)"
|
||||
oldCursorState.top = newCursorState.top
|
||||
oldCursorState.left = newCursorState.left
|
||||
|
||||
if newCursorState.height isnt oldCursorState.height
|
||||
cursorNode.style.height = newCursorState.height + 'px'
|
||||
oldCursorState.height = newCursorState.height
|
||||
|
||||
if newCursorState.width isnt oldCursorState.width
|
||||
cursorNode.style.width = newCursorState.width + 'px'
|
||||
oldCursorState.width = newCursorState.width
|
||||
|
@ -1,62 +1,47 @@
|
||||
_ = require 'underscore-plus'
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqual, isEqualForProperties, multiplyString, toArray} = _
|
||||
Decoration = require './decoration'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
|
||||
WrapperDiv = document.createElement('div')
|
||||
|
||||
module.exports =
|
||||
GutterComponent = React.createClass
|
||||
displayName: 'GutterComponent'
|
||||
mixins: [SubscriberMixin]
|
||||
|
||||
maxLineNumberDigits: null
|
||||
class GutterComponent
|
||||
dummyLineNumberNode: null
|
||||
measuredWidth: null
|
||||
|
||||
render: ->
|
||||
{presenter} = @props
|
||||
@newState = presenter.state.gutter
|
||||
@oldState ?= {lineNumbers: {}}
|
||||
|
||||
{scrollHeight, backgroundColor} = @newState
|
||||
|
||||
div className: 'gutter',
|
||||
div className: 'line-numbers', ref: 'lineNumbers', style:
|
||||
height: scrollHeight
|
||||
WebkitTransform: @getTransform()
|
||||
backgroundColor: backgroundColor
|
||||
|
||||
getTransform: ->
|
||||
{useHardwareAcceleration} = @props
|
||||
{scrollTop} = @newState
|
||||
|
||||
if useHardwareAcceleration
|
||||
"translate3d(0px, #{-scrollTop}px, 0px)"
|
||||
else
|
||||
"translate(0px, #{-scrollTop}px)"
|
||||
|
||||
componentWillMount: ->
|
||||
constructor: ({@presenter, @onMouseDown, @editor}) ->
|
||||
@lineNumberNodesById = {}
|
||||
|
||||
componentDidMount: ->
|
||||
{@maxLineNumberDigits} = @newState
|
||||
@appendDummyLineNumber()
|
||||
@updateLineNumbers()
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.classList.add('gutter')
|
||||
@lineNumbersNode = document.createElement('div')
|
||||
@lineNumbersNode.classList.add('line-numbers')
|
||||
@domNode.appendChild(@lineNumbersNode)
|
||||
|
||||
node = @getDOMNode()
|
||||
node.addEventListener 'click', @onClick
|
||||
node.addEventListener 'mousedown', @onMouseDown
|
||||
@domNode.addEventListener 'click', @onClick
|
||||
@domNode.addEventListener 'mousedown', @onMouseDown
|
||||
|
||||
componentDidUpdate: (oldProps) ->
|
||||
{maxLineNumberDigits} = @newState
|
||||
unless maxLineNumberDigits is @maxLineNumberDigits
|
||||
@maxLineNumberDigits = maxLineNumberDigits
|
||||
@updateSync()
|
||||
|
||||
updateSync: ->
|
||||
@newState = @presenter.state.gutter
|
||||
@oldState ?= {lineNumbers: {}}
|
||||
|
||||
@appendDummyLineNumber() unless @dummyLineNumberNode?
|
||||
|
||||
if @newState.scrollHeight isnt @oldState.scrollHeight
|
||||
@lineNumbersNode.style.height = @newState.scrollHeight + 'px'
|
||||
@oldState.scrollHeight = @newState.scrollHeight
|
||||
|
||||
if @newState.scrollTop isnt @oldState.scrollTop
|
||||
@lineNumbersNode.style['-webkit-transform'] = "translate3d(0px, #{-@newState.scrollTop}px, 0px)"
|
||||
@oldState.scrollTop = @newState.scrollTop
|
||||
|
||||
if @newState.backgroundColor isnt @oldState.backgroundColor
|
||||
@lineNumbersNode.style.backgroundColor = @newState.backgroundColor
|
||||
@oldState.backgroundColor = @newState.backgroundColor
|
||||
|
||||
if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits
|
||||
@updateDummyLineNumber()
|
||||
node.remove() for id, node of @lineNumberNodesById
|
||||
@oldState = {lineNumbers: {}}
|
||||
@oldState = {maxLineNumberDigits: @newState.maxLineNumberDigits, lineNumbers: {}}
|
||||
@lineNumberNodesById = {}
|
||||
|
||||
@updateLineNumbers()
|
||||
@ -66,7 +51,7 @@ GutterComponent = React.createClass
|
||||
appendDummyLineNumber: ->
|
||||
WrapperDiv.innerHTML = @buildLineNumberHTML({bufferRow: -1})
|
||||
@dummyLineNumberNode = WrapperDiv.children[0]
|
||||
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
|
||||
@lineNumbersNode.appendChild(@dummyLineNumberNode)
|
||||
|
||||
updateDummyLineNumber: ->
|
||||
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false)
|
||||
@ -87,9 +72,9 @@ GutterComponent = React.createClass
|
||||
|
||||
if newLineNumberIds?
|
||||
WrapperDiv.innerHTML = newLineNumbersHTML
|
||||
newLineNumberNodes = toArray(WrapperDiv.children)
|
||||
newLineNumberNodes = _.toArray(WrapperDiv.children)
|
||||
|
||||
node = @refs.lineNumbers.getDOMNode()
|
||||
node = @lineNumbersNode
|
||||
for id, i in newLineNumberIds
|
||||
lineNumberNode = newLineNumberNodes[i]
|
||||
@lineNumberNodesById[id] = lineNumberNode
|
||||
@ -120,7 +105,7 @@ GutterComponent = React.createClass
|
||||
else
|
||||
lineNumber = (bufferRow + 1).toString()
|
||||
|
||||
padding = multiplyString(' ', maxLineNumberDigits - lineNumber.length)
|
||||
padding = _.multiplyString(' ', maxLineNumberDigits - lineNumber.length)
|
||||
iconHTML = '<div class="icon-right"></div>'
|
||||
padding + lineNumber + iconHTML
|
||||
|
||||
@ -151,21 +136,20 @@ GutterComponent = React.createClass
|
||||
return @lineNumberNodesById[id]
|
||||
null
|
||||
|
||||
onMouseDown: (event) ->
|
||||
onMouseDown: (event) =>
|
||||
{target} = event
|
||||
lineNumber = target.parentNode
|
||||
|
||||
unless target.classList.contains('icon-right') and lineNumber.classList.contains('foldable')
|
||||
@props.onMouseDown(event)
|
||||
@onMouseDown(event)
|
||||
|
||||
onClick: (event) ->
|
||||
{editor} = @props
|
||||
onClick: (event) =>
|
||||
{target} = event
|
||||
lineNumber = target.parentNode
|
||||
|
||||
if target.classList.contains('icon-right') and lineNumber.classList.contains('foldable')
|
||||
bufferRow = parseInt(lineNumber.getAttribute('data-buffer-row'))
|
||||
if lineNumber.classList.contains('folded')
|
||||
editor.unfoldBufferRow(bufferRow)
|
||||
@editor.unfoldBufferRow(bufferRow)
|
||||
else
|
||||
editor.foldBufferRow(bufferRow)
|
||||
@editor.foldBufferRow(bufferRow)
|
||||
|
@ -1,50 +0,0 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqualForProperties} = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
HighlightComponent = React.createClass
|
||||
displayName: 'HighlightComponent'
|
||||
currentFlashCount: 0
|
||||
currentFlashClass: null
|
||||
|
||||
render: ->
|
||||
{state} = @props
|
||||
|
||||
className = 'highlight'
|
||||
className += " #{state.class}" if state.class?
|
||||
|
||||
div {className},
|
||||
for region, i in state.regions
|
||||
regionClassName = 'region'
|
||||
regionClassName += " #{state.deprecatedRegionClass}" if state.deprecatedRegionClass?
|
||||
div className: regionClassName, key: i, style: region
|
||||
|
||||
componentDidMount: ->
|
||||
@flashIfRequested()
|
||||
|
||||
componentDidUpdate: ->
|
||||
@flashIfRequested()
|
||||
|
||||
flashIfRequested: ->
|
||||
if @props.state.flashCount > @currentFlashCount
|
||||
@currentFlashCount = @props.state.flashCount
|
||||
|
||||
node = @getDOMNode()
|
||||
{flashClass, flashDuration} = @props.state
|
||||
|
||||
addFlashClass = =>
|
||||
node.classList.add(flashClass)
|
||||
@currentFlashClass = flashClass
|
||||
@flashTimeoutId = setTimeout(removeFlashClass, flashDuration)
|
||||
|
||||
removeFlashClass = =>
|
||||
node.classList.remove(@currentFlashClass)
|
||||
@currentFlashClass = null
|
||||
clearTimeout(@flashTimeoutId)
|
||||
|
||||
if @currentFlashClass?
|
||||
removeFlashClass()
|
||||
requestAnimationFrame(addFlashClass)
|
||||
else
|
||||
addFlashClass()
|
@ -1,25 +1,107 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqualForProperties} = require 'underscore-plus'
|
||||
HighlightComponent = require './highlight-component'
|
||||
RegionStyleProperties = ['top', 'left', 'right', 'width', 'height']
|
||||
|
||||
module.exports =
|
||||
HighlightsComponent = React.createClass
|
||||
displayName: 'HighlightsComponent'
|
||||
class HighlightsComponent
|
||||
oldState: null
|
||||
|
||||
render: ->
|
||||
div className: 'highlights',
|
||||
@renderHighlights()
|
||||
constructor: (@presenter) ->
|
||||
@highlightNodesById = {}
|
||||
@regionNodesByHighlightId = {}
|
||||
|
||||
renderHighlights: ->
|
||||
{presenter} = @props
|
||||
highlightComponents = []
|
||||
for key, state of presenter.state.content.highlights
|
||||
highlightComponents.push(HighlightComponent({key, state}))
|
||||
highlightComponents
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.classList.add('highlights')
|
||||
|
||||
componentDidMount: ->
|
||||
if atom.config.get('editor.useShadowDOM')
|
||||
insertionPoint = document.createElement('content')
|
||||
insertionPoint.setAttribute('select', '.underlayer')
|
||||
@getDOMNode().appendChild(insertionPoint)
|
||||
@domNode.appendChild(insertionPoint)
|
||||
|
||||
updateSync: ->
|
||||
newState = @presenter.state.content.highlights
|
||||
@oldState ?= {}
|
||||
|
||||
# remove highlights
|
||||
for id of @oldState
|
||||
unless newState[id]?
|
||||
@highlightNodesById[id].remove()
|
||||
delete @highlightNodesById[id]
|
||||
delete @regionNodesByHighlightId[id]
|
||||
delete @oldState[id]
|
||||
|
||||
# add or update highlights
|
||||
for id, highlightState of newState
|
||||
unless @oldState[id]?
|
||||
highlightNode = document.createElement('div')
|
||||
highlightNode.classList.add('highlight')
|
||||
@highlightNodesById[id] = highlightNode
|
||||
@regionNodesByHighlightId[id] = {}
|
||||
@domNode.appendChild(highlightNode)
|
||||
@updateHighlightNode(id, highlightState)
|
||||
|
||||
updateHighlightNode: (id, newHighlightState) ->
|
||||
highlightNode = @highlightNodesById[id]
|
||||
oldHighlightState = (@oldState[id] ?= {regions: [], flashCount: 0})
|
||||
|
||||
# update class
|
||||
if newHighlightState.class isnt oldHighlightState.class
|
||||
highlightNode.classList.remove(oldHighlightState.class) if oldHighlightState.class?
|
||||
highlightNode.classList.add(newHighlightState.class)
|
||||
oldHighlightState.class = newHighlightState.class
|
||||
|
||||
@updateHighlightRegions(id, newHighlightState)
|
||||
@flashHighlightNodeIfRequested(id, newHighlightState)
|
||||
|
||||
updateHighlightRegions: (id, newHighlightState) ->
|
||||
oldHighlightState = @oldState[id]
|
||||
highlightNode = @highlightNodesById[id]
|
||||
|
||||
# remove regions
|
||||
while oldHighlightState.regions.length > newHighlightState.regions.length
|
||||
oldHighlightState.regions.pop()
|
||||
@regionNodesByHighlightId[id][oldHighlightState.regions.length].remove()
|
||||
delete @regionNodesByHighlightId[id][oldHighlightState.regions.length]
|
||||
|
||||
# add or update regions
|
||||
for newRegionState, i in newHighlightState.regions
|
||||
unless oldHighlightState.regions[i]?
|
||||
oldHighlightState.regions[i] = {}
|
||||
regionNode = document.createElement('div')
|
||||
regionNode.classList.add('region')
|
||||
regionNode.classList.add(newHighlightState.deprecatedRegionClass) if newHighlightState.deprecatedRegionClass?
|
||||
@regionNodesByHighlightId[id][i] = regionNode
|
||||
highlightNode.appendChild(regionNode)
|
||||
|
||||
oldRegionState = oldHighlightState.regions[i]
|
||||
regionNode = @regionNodesByHighlightId[id][i]
|
||||
|
||||
for property in RegionStyleProperties
|
||||
if newRegionState[property] isnt oldRegionState[property]
|
||||
oldRegionState[property] = newRegionState[property]
|
||||
if newRegionState[property]?
|
||||
regionNode.style[property] = newRegionState[property] + 'px'
|
||||
else
|
||||
regionNode.style[property] = ''
|
||||
|
||||
flashHighlightNodeIfRequested: (id, newHighlightState) ->
|
||||
oldHighlightState = @oldState[id]
|
||||
return unless newHighlightState.flashCount > oldHighlightState.flashCount
|
||||
|
||||
highlightNode = @highlightNodesById[id]
|
||||
|
||||
addFlashClass = =>
|
||||
highlightNode.classList.add(newHighlightState.flashClass)
|
||||
oldHighlightState.flashClass = newHighlightState.flashClass
|
||||
@flashTimeoutId = setTimeout(removeFlashClass, newHighlightState.flashDuration)
|
||||
|
||||
removeFlashClass = =>
|
||||
highlightNode.classList.remove(oldHighlightState.flashClass)
|
||||
oldHighlightState.flashClass = null
|
||||
clearTimeout(@flashTimeoutId)
|
||||
|
||||
if oldHighlightState.flashClass?
|
||||
removeFlashClass()
|
||||
requestAnimationFrame(addFlashClass)
|
||||
else
|
||||
addFlashClass()
|
||||
|
||||
oldHighlightState.flashCount = newHighlightState.flashCount
|
||||
|
@ -1,39 +1,29 @@
|
||||
{last, isEqual} = require 'underscore-plus'
|
||||
React = require 'react-atom-fork'
|
||||
{input} = require 'reactionary-atom-fork'
|
||||
|
||||
module.exports =
|
||||
InputComponent = React.createClass
|
||||
displayName: 'InputComponent'
|
||||
class InputComponent
|
||||
constructor: (@presenter) ->
|
||||
@domNode = document.createElement('input')
|
||||
@domNode.classList.add('hidden-input')
|
||||
@domNode.setAttribute('data-react-skip-selection-restoration', true)
|
||||
@domNode.style['-webkit-transform'] = 'translateZ(0)'
|
||||
@domNode.addEventListener 'paste', (event) => event.preventDefault()
|
||||
@updateSync()
|
||||
|
||||
render: ->
|
||||
{className, style} = @props
|
||||
updateSync: ->
|
||||
@oldState ?= {}
|
||||
newState = @presenter.state.hiddenInput
|
||||
|
||||
input {className, style, 'data-react-skip-selection-restoration': true}
|
||||
if newState.top isnt @oldState.top
|
||||
@domNode.style.top = newState.top + 'px'
|
||||
@oldState.top = newState.top
|
||||
|
||||
getInitialState: ->
|
||||
{lastChar: ''}
|
||||
if newState.left isnt @oldState.left
|
||||
@domNode.style.left = newState.left + 'px'
|
||||
@oldState.left = newState.left
|
||||
|
||||
componentDidMount: ->
|
||||
node = @getDOMNode()
|
||||
node.addEventListener 'paste', @onPaste
|
||||
node.addEventListener 'compositionupdate', @onCompositionUpdate
|
||||
if newState.width isnt @oldState.width
|
||||
@domNode.style.width = newState.width + 'px'
|
||||
@oldState.width = newState.width
|
||||
|
||||
# Don't let text accumulate in the input forever, but avoid excessive reflows
|
||||
componentDidUpdate: ->
|
||||
if @lastValueLength > 500 and not @isPressAndHoldCharacter(@state.lastChar)
|
||||
@getDOMNode().value = ''
|
||||
@lastValueLength = 0
|
||||
|
||||
# This should actually consult the property lists in /System/Library/Input Methods/PressAndHold.app
|
||||
isPressAndHoldCharacter: (char) ->
|
||||
@state.lastChar.match /[aeiouAEIOU]/
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqual(newProps.style, @props.style)
|
||||
|
||||
onPaste: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
focus: ->
|
||||
@getDOMNode().focus()
|
||||
if newState.height isnt @oldState.height
|
||||
@domNode.style.height = newState.height + 'px'
|
||||
@oldState.height = newState.height
|
||||
|
@ -1,7 +1,5 @@
|
||||
_ = require 'underscore-plus'
|
||||
React = require 'react-atom-fork'
|
||||
{div, span} = require 'reactionary-atom-fork'
|
||||
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
||||
{toArray} = require 'underscore-plus'
|
||||
{$$} = require 'space-pen'
|
||||
|
||||
CursorsComponent = require './cursors-component'
|
||||
@ -12,73 +10,85 @@ DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibi
|
||||
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
|
||||
WrapperDiv = document.createElement('div')
|
||||
|
||||
cloneObject = (object) ->
|
||||
clone = {}
|
||||
clone[key] = value for key, value of object
|
||||
clone
|
||||
|
||||
module.exports =
|
||||
LinesComponent = React.createClass
|
||||
displayName: 'LinesComponent'
|
||||
class LinesComponent
|
||||
placeholderTextDiv: null
|
||||
|
||||
render: ->
|
||||
{editor, presenter} = @props
|
||||
@oldState ?= {lines: {}}
|
||||
@newState = presenter.state.content
|
||||
|
||||
{scrollHeight, scrollWidth, backgroundColor, placeholderText} = @newState
|
||||
|
||||
style =
|
||||
height: scrollHeight
|
||||
width: scrollWidth
|
||||
WebkitTransform: @getTransform()
|
||||
backgroundColor: backgroundColor
|
||||
|
||||
div {className: 'lines', style},
|
||||
div className: 'placeholder-text', placeholderText if placeholderText?
|
||||
CursorsComponent {presenter}
|
||||
HighlightsComponent {presenter}
|
||||
|
||||
getTransform: ->
|
||||
{scrollTop, scrollLeft} = @newState
|
||||
{useHardwareAcceleration} = @props
|
||||
|
||||
if useHardwareAcceleration
|
||||
"translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
|
||||
else
|
||||
"translate(#{-scrollLeft}px, #{-scrollTop}px)"
|
||||
|
||||
componentWillMount: ->
|
||||
constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) ->
|
||||
@measuredLines = new Set
|
||||
@lineNodesByLineId = {}
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@renderedDecorationsByLineId = {}
|
||||
|
||||
componentDidMount: ->
|
||||
if @props.useShadowDOM
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.classList.add('lines')
|
||||
|
||||
@cursorsComponent = new CursorsComponent(@presenter)
|
||||
@domNode.appendChild(@cursorsComponent.domNode)
|
||||
|
||||
@highlightsComponent = new HighlightsComponent(@presenter)
|
||||
@domNode.appendChild(@highlightsComponent.domNode)
|
||||
|
||||
if @useShadowDOM
|
||||
insertionPoint = document.createElement('content')
|
||||
insertionPoint.setAttribute('select', '.overlayer')
|
||||
@getDOMNode().appendChild(insertionPoint)
|
||||
@domNode.appendChild(insertionPoint)
|
||||
|
||||
insertionPoint = document.createElement('content')
|
||||
insertionPoint.setAttribute('select', 'atom-overlay')
|
||||
@overlayManager = new OverlayManager(@props.hostElement)
|
||||
@getDOMNode().appendChild(insertionPoint)
|
||||
@overlayManager = new OverlayManager(@hostElement)
|
||||
@domNode.appendChild(insertionPoint)
|
||||
else
|
||||
@overlayManager = new OverlayManager(@getDOMNode())
|
||||
@overlayManager = new OverlayManager(@domNode)
|
||||
|
||||
componentDidUpdate: ->
|
||||
{visible, presenter} = @props
|
||||
@updateSync(visible)
|
||||
|
||||
updateSync: ->
|
||||
@newState = @presenter.state.content
|
||||
@oldState ?= {lines: {}}
|
||||
|
||||
if @newState.scrollHeight isnt @oldState.scrollHeight
|
||||
@domNode.style.height = @newState.scrollHeight + 'px'
|
||||
@oldState.scrollHeight = @newState.scrollHeight
|
||||
|
||||
if @newState.scrollTop isnt @oldState.scrollTop or @newState.scrollLeft isnt @oldState.scrollLeft
|
||||
@domNode.style['-webkit-transform'] = "translate3d(#{-@newState.scrollLeft}px, #{-@newState.scrollTop}px, 0px)"
|
||||
@oldState.scrollTop = @newState.scrollTop
|
||||
@oldState.scrollLeft = @newState.scrollLeft
|
||||
|
||||
if @newState.backgroundColor isnt @oldState.backgroundColor
|
||||
@domNode.style.backgroundColor = @newState.backgroundColor
|
||||
@oldState.backgroundColor = @newState.backgroundColor
|
||||
|
||||
if @newState.placeholderText isnt @oldState.placeholderText
|
||||
@placeholderTextDiv?.remove()
|
||||
if @newState.placeholderText?
|
||||
@placeholderTextDiv = document.createElement('div')
|
||||
@placeholderTextDiv.classList.add('placeholder-text')
|
||||
@placeholderTextDiv.textContent = @newState.placeholderText
|
||||
@domNode.appendChild(@placeholderTextDiv)
|
||||
|
||||
@removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
|
||||
@updateLineNodes()
|
||||
@measureCharactersInNewLines() if visible and not @newState.scrollingVertically
|
||||
|
||||
@overlayManager?.render(@props)
|
||||
if @newState.scrollWidth isnt @oldState.scrollWidth
|
||||
@domNode.style.width = @newState.scrollWidth + 'px'
|
||||
@oldState.scrollWidth = @newState.scrollWidth
|
||||
|
||||
@cursorsComponent.updateSync()
|
||||
@highlightsComponent.updateSync()
|
||||
|
||||
@overlayManager?.render(@presenter)
|
||||
|
||||
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
|
||||
@oldState.scrollWidth = @newState.scrollWidth
|
||||
|
||||
clearScreenRowCaches: ->
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
|
||||
removeLineNodes: ->
|
||||
@removeLineNode(id) for id of @oldState.lines
|
||||
|
||||
@ -90,8 +100,6 @@ LinesComponent = React.createClass
|
||||
delete @oldState.lines[id]
|
||||
|
||||
updateLineNodes: ->
|
||||
{presenter} = @props
|
||||
|
||||
for id of @oldState.lines
|
||||
unless @newState.lines.hasOwnProperty(id)
|
||||
@removeLineNode(id)
|
||||
@ -109,20 +117,18 @@ LinesComponent = React.createClass
|
||||
newLinesHTML += @buildLineHTML(id)
|
||||
@screenRowsByLineId[id] = lineState.screenRow
|
||||
@lineIdsByScreenRow[lineState.screenRow] = id
|
||||
@oldState.lines[id] = _.clone(lineState)
|
||||
@oldState.lines[id] = cloneObject(lineState)
|
||||
|
||||
return unless newLineIds?
|
||||
|
||||
WrapperDiv.innerHTML = newLinesHTML
|
||||
newLineNodes = toArray(WrapperDiv.children)
|
||||
node = @getDOMNode()
|
||||
newLineNodes = _.toArray(WrapperDiv.children)
|
||||
for id, i in newLineIds
|
||||
lineNode = newLineNodes[i]
|
||||
@lineNodesByLineId[id] = lineNode
|
||||
node.appendChild(lineNode)
|
||||
@domNode.appendChild(lineNode)
|
||||
|
||||
buildLineHTML: (id) ->
|
||||
{presenter} = @props
|
||||
{scrollWidth} = @newState
|
||||
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id]
|
||||
|
||||
@ -167,7 +173,6 @@ LinesComponent = React.createClass
|
||||
@buildEndOfLineHTML(id) or ' '
|
||||
|
||||
buildLineInnerHTML: (id) ->
|
||||
{editor} = @props
|
||||
{indentGuidesVisible} = @newState
|
||||
{tokens, text, isOnlyWhitespace} = @newState.lines[id]
|
||||
innerHTML = ""
|
||||
@ -217,13 +222,16 @@ LinesComponent = React.createClass
|
||||
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
|
||||
|
||||
updateLineNode: (id) ->
|
||||
{scrollWidth} = @newState
|
||||
{screenRow, top} = @newState.lines[id]
|
||||
oldLineState = @oldState.lines[id]
|
||||
newLineState = @newState.lines[id]
|
||||
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
|
||||
newDecorationClasses = @newState.lines[id].decorationClasses
|
||||
oldDecorationClasses = @oldState.lines[id].decorationClasses
|
||||
if @newState.scrollWidth isnt @oldState.scrollWidth
|
||||
lineNode.style.width = @newState.scrollWidth + 'px'
|
||||
|
||||
newDecorationClasses = newLineState.decorationClasses
|
||||
oldDecorationClasses = oldLineState.decorationClasses
|
||||
|
||||
if oldDecorationClasses?
|
||||
for decorationClass in oldDecorationClasses
|
||||
@ -235,36 +243,37 @@ LinesComponent = React.createClass
|
||||
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
|
||||
lineNode.classList.add(decorationClass)
|
||||
|
||||
lineNode.style.width = scrollWidth + 'px'
|
||||
lineNode.style.top = top + 'px'
|
||||
lineNode.dataset.screenRow = screenRow
|
||||
@screenRowsByLineId[id] = screenRow
|
||||
@lineIdsByScreenRow[screenRow] = id
|
||||
oldLineState.decorationClasses = newLineState.decorationClasses
|
||||
|
||||
if newLineState.top isnt oldLineState.top
|
||||
lineNode.style.top = newLineState.top + 'px'
|
||||
oldLineState.top = newLineState.cop
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
lineNode.dataset.screenRow = newLineState.screenRow
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
@lineIdsByScreenRow[newLineState.screenRow] = id
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||
|
||||
measureLineHeightAndDefaultCharWidth: ->
|
||||
node = @getDOMNode()
|
||||
node.appendChild(DummyLineNode)
|
||||
@domNode.appendChild(DummyLineNode)
|
||||
lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
|
||||
charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
|
||||
node.removeChild(DummyLineNode)
|
||||
@domNode.removeChild(DummyLineNode)
|
||||
|
||||
{editor, presenter} = @props
|
||||
presenter.setLineHeight(lineHeightInPixels)
|
||||
presenter.setBaseCharacterWidth(charWidth)
|
||||
@presenter.setLineHeight(lineHeightInPixels)
|
||||
@presenter.setBaseCharacterWidth(charWidth)
|
||||
|
||||
remeasureCharacterWidths: ->
|
||||
return unless @props.presenter.baseCharacterWidth
|
||||
return unless @presenter.baseCharacterWidth
|
||||
|
||||
@clearScopedCharWidths()
|
||||
@measureCharactersInNewLines()
|
||||
|
||||
measureCharactersInNewLines: ->
|
||||
{presenter} = @props
|
||||
|
||||
presenter.batchCharacterMeasurement =>
|
||||
@presenter.batchCharacterMeasurement =>
|
||||
for id, lineState of @oldState.lines
|
||||
unless @measuredLines.has(id)
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
@ -272,13 +281,12 @@ LinesComponent = React.createClass
|
||||
return
|
||||
|
||||
measureCharactersInLine: (tokenizedLine, lineNode) ->
|
||||
{editor} = @props
|
||||
rangeForMeasurement = null
|
||||
iterator = null
|
||||
charIndex = 0
|
||||
|
||||
for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens
|
||||
charWidths = editor.getScopedCharWidths(scopes)
|
||||
charWidths = @presenter.getScopedCharacterWidths(scopes)
|
||||
|
||||
valueIndex = 0
|
||||
while valueIndex < value.length
|
||||
@ -310,7 +318,7 @@ LinesComponent = React.createClass
|
||||
rangeForMeasurement.setStart(textNode, i)
|
||||
rangeForMeasurement.setEnd(textNode, i + charLength)
|
||||
charWidth = rangeForMeasurement.getBoundingClientRect().width
|
||||
@props.presenter.setScopedCharacterWidth(scopes, char, charWidth)
|
||||
@presenter.setScopedCharacterWidth(scopes, char, charWidth)
|
||||
|
||||
charIndex += charLength
|
||||
|
||||
@ -318,5 +326,4 @@ LinesComponent = React.createClass
|
||||
|
||||
clearScopedCharWidths: ->
|
||||
@measuredLines.clear()
|
||||
@props.editor.clearScopedCharWidths()
|
||||
@props.presenter.clearScopedCharacterWidths()
|
||||
@presenter.clearScopedCharacterWidths()
|
||||
|
@ -3,16 +3,14 @@ class OverlayManager
|
||||
constructor: (@container) ->
|
||||
@overlayNodesById = {}
|
||||
|
||||
render: (props) ->
|
||||
{presenter} = props
|
||||
|
||||
render: (presenter) ->
|
||||
for decorationId, {pixelPosition, item} of presenter.state.content.overlays
|
||||
@renderOverlay(presenter, decorationId, item, pixelPosition)
|
||||
|
||||
for id, overlayNode of @overlayNodesById
|
||||
unless presenter.state.content.overlays.hasOwnProperty(id)
|
||||
overlayNode.remove()
|
||||
delete @overlayNodesById[id]
|
||||
overlayNode.remove()
|
||||
|
||||
return
|
||||
|
||||
|
@ -1,69 +1,74 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{extend, isEqualForProperties} = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
ScrollbarComponent = React.createClass
|
||||
displayName: 'ScrollbarComponent'
|
||||
class ScrollbarComponent
|
||||
constructor: ({@presenter, @orientation, @onScroll}) ->
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.classList.add "#{@orientation}-scrollbar"
|
||||
@domNode.style['-webkit-transform'] = 'translateZ(0)' # See atom/atom#3559
|
||||
@domNode.style.left = 0 if @orientation is 'horizontal'
|
||||
|
||||
render: ->
|
||||
{presenter, orientation, className, useHardwareAcceleration} = @props
|
||||
@contentNode = document.createElement('div')
|
||||
@contentNode.classList.add "scrollbar-content"
|
||||
@domNode.appendChild(@contentNode)
|
||||
|
||||
switch orientation
|
||||
@domNode.addEventListener 'scroll', @onScrollCallback
|
||||
|
||||
@updateSync()
|
||||
|
||||
updateSync: ->
|
||||
@oldState ?= {}
|
||||
switch @orientation
|
||||
when 'vertical'
|
||||
@newState = presenter.state.verticalScrollbar
|
||||
@newState = @presenter.state.verticalScrollbar
|
||||
@updateVertical()
|
||||
when 'horizontal'
|
||||
@newState = presenter.state.horizontalScrollbar
|
||||
@newState = @presenter.state.horizontalScrollbar
|
||||
@updateHorizontal()
|
||||
|
||||
style = {}
|
||||
if @newState.visible isnt @oldState.visible
|
||||
if @newState.visible
|
||||
@domNode.style.display = ''
|
||||
else
|
||||
@domNode.style.display = 'none'
|
||||
@oldState.visible = @newState.visible
|
||||
|
||||
style.display = 'none' unless @newState.visible
|
||||
style.transform = 'translateZ(0)' if useHardwareAcceleration # See atom/atom#3559
|
||||
switch orientation
|
||||
updateVertical: ->
|
||||
if @newState.width isnt @oldState.width
|
||||
@domNode.style.width = @newState.width + 'px'
|
||||
@oldState.width = @newState.width
|
||||
|
||||
if @newState.bottom isnt @oldState.bottom
|
||||
@domNode.style.bottom = @newState.bottom + 'px'
|
||||
@oldState.bottom = @newState.bottom
|
||||
|
||||
if @newState.scrollHeight isnt @oldState.scrollHeight
|
||||
@contentNode.style.height = @newState.scrollHeight + 'px'
|
||||
@oldState.scrollHeight = @newState.scrollHeight
|
||||
|
||||
if @newState.scrollTop isnt @oldState.scrollTop
|
||||
@domNode.scrollTop = @newState.scrollTop
|
||||
@oldState.scrollTop = @newState.scrollTop
|
||||
|
||||
updateHorizontal: ->
|
||||
if @newState.height isnt @oldState.height
|
||||
@domNode.style.height = @newState.height + 'px'
|
||||
@oldState.height = @newState.height
|
||||
|
||||
if @newState.right isnt @oldState.right
|
||||
@domNode.style.right = @newState.right + 'px'
|
||||
@oldState.right = @newState.right
|
||||
|
||||
if @newState.scrollWidth isnt @oldState.scrollWidth
|
||||
@contentNode.style.width = @newState.scrollWidth + 'px'
|
||||
@oldState.scrollWidth = @newState.scrollWidth
|
||||
|
||||
if @newState.scrollLeft isnt @oldState.scrollLeft
|
||||
@domNode.scrollLeft = @newState.scrollLeft
|
||||
@oldState.scrollLeft = @newState.scrollLeft
|
||||
|
||||
|
||||
onScrollCallback: =>
|
||||
switch @orientation
|
||||
when 'vertical'
|
||||
style.width = @newState.width
|
||||
style.bottom = @newState.bottom
|
||||
@onScroll(@domNode.scrollTop)
|
||||
when 'horizontal'
|
||||
style.left = 0
|
||||
style.right = @newState.right
|
||||
style.height = @newState.height
|
||||
|
||||
div {className, style},
|
||||
switch orientation
|
||||
when 'vertical'
|
||||
div className: 'scrollbar-content', style: {height: @newState.scrollHeight}
|
||||
when 'horizontal'
|
||||
div className: 'scrollbar-content', style: {width: @newState.scrollWidth}
|
||||
|
||||
componentDidMount: ->
|
||||
{orientation} = @props
|
||||
|
||||
unless orientation is 'vertical' or orientation is 'horizontal'
|
||||
throw new Error("Must specify an orientation property of 'vertical' or 'horizontal'")
|
||||
|
||||
@getDOMNode().addEventListener 'scroll', @onScroll
|
||||
|
||||
componentWillUnmount: ->
|
||||
@getDOMNode().removeEventListener 'scroll', @onScroll
|
||||
|
||||
componentDidUpdate: ->
|
||||
{orientation} = @props
|
||||
node = @getDOMNode()
|
||||
|
||||
switch orientation
|
||||
when 'vertical'
|
||||
node.scrollTop = @newState.scrollTop
|
||||
when 'horizontal'
|
||||
node.scrollLeft = @newState.scrollLeft
|
||||
|
||||
onScroll: ->
|
||||
{orientation, onScroll} = @props
|
||||
node = @getDOMNode()
|
||||
|
||||
switch orientation
|
||||
when 'vertical'
|
||||
scrollTop = node.scrollTop
|
||||
onScroll(scrollTop)
|
||||
when 'horizontal'
|
||||
scrollLeft = node.scrollLeft
|
||||
onScroll(scrollLeft)
|
||||
@onScroll(@domNode.scrollLeft)
|
||||
|
@ -1,25 +1,37 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqualForProperties} = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
ScrollbarCornerComponent = React.createClass
|
||||
displayName: 'ScrollbarCornerComponent'
|
||||
class ScrollbarCornerComponent
|
||||
constructor: (@presenter) ->
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.classList.add('scrollbar-corner')
|
||||
|
||||
render: ->
|
||||
{presenter, measuringScrollbars} = @props
|
||||
@contentNode = document.createElement('div')
|
||||
@domNode.appendChild(@contentNode)
|
||||
|
||||
visible = presenter.state.horizontalScrollbar.visible and presenter.state.verticalScrollbar.visible
|
||||
width = presenter.state.verticalScrollbar.width
|
||||
height = presenter.state.horizontalScrollbar.height
|
||||
@updateSync()
|
||||
|
||||
if measuringScrollbars
|
||||
height = 25
|
||||
width = 25
|
||||
updateSync: ->
|
||||
@oldState ?= {}
|
||||
@newState ?= {}
|
||||
|
||||
display = 'none' unless visible
|
||||
newHorizontalState = @presenter.state.horizontalScrollbar
|
||||
newVerticalState = @presenter.state.verticalScrollbar
|
||||
@newState.visible = newHorizontalState.visible and newVerticalState.visible
|
||||
@newState.height = newHorizontalState.height
|
||||
@newState.width = newVerticalState.width
|
||||
|
||||
div className: 'scrollbar-corner', style: {display, width, height},
|
||||
div style:
|
||||
height: height + 1
|
||||
width: width + 1
|
||||
if @newState.visible isnt @oldState.visible
|
||||
if @newState.visible
|
||||
@domNode.style.display = ''
|
||||
else
|
||||
@domNode.style.display = 'none'
|
||||
@oldState.visible = @newState.visible
|
||||
|
||||
if @newState.height isnt @oldState.height
|
||||
@domNode.style.height = @newState.height + 'px'
|
||||
@contentNode.style.height = @newState.height + 1 + 'px'
|
||||
@oldState.height = @newState.height
|
||||
|
||||
if @newState.width isnt @oldState.width
|
||||
@domNode.style.width = @newState.width + 'px'
|
||||
@contentNode.style.width = @newState.width + 1 + 'px'
|
||||
@oldState.width = @newState.width
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,5 @@
|
||||
{Emitter} = require 'event-kit'
|
||||
{View, $, callRemoveHooks} = require 'space-pen'
|
||||
React = require 'react-atom-fork'
|
||||
Path = require 'path'
|
||||
{defaults} = require 'underscore-plus'
|
||||
TextBuffer = require 'text-buffer'
|
||||
@ -61,7 +60,7 @@ class TextEditorElement extends HTMLElement
|
||||
|
||||
attachedCallback: ->
|
||||
@buildModel() unless @getModel()?
|
||||
@mountComponent() unless @component?.isMounted()
|
||||
@mountComponent() unless @component?
|
||||
@component.checkForVisibilityChange()
|
||||
if this is document.activeElement
|
||||
@focused()
|
||||
@ -105,7 +104,7 @@ class TextEditorElement extends HTMLElement
|
||||
))
|
||||
|
||||
mountComponent: ->
|
||||
@componentDescriptor ?= TextEditorComponent(
|
||||
@component = new TextEditorComponent(
|
||||
hostElement: this
|
||||
rootElement: @rootElement
|
||||
stylesElement: @stylesElement
|
||||
@ -113,27 +112,28 @@ class TextEditorElement extends HTMLElement
|
||||
lineOverdrawMargin: @lineOverdrawMargin
|
||||
useShadowDOM: @useShadowDOM
|
||||
)
|
||||
@component = React.renderComponent(@componentDescriptor, @rootElement)
|
||||
@rootElement.appendChild(@component.domNode)
|
||||
|
||||
if @useShadowDOM
|
||||
@shadowRoot.addEventListener('blur', @shadowRootBlurred.bind(this), true)
|
||||
else
|
||||
inputNode = @component.refs.input.getDOMNode()
|
||||
inputNode = @component.hiddenInputComponent.domNode
|
||||
inputNode.addEventListener 'focus', @focused.bind(this)
|
||||
inputNode.addEventListener 'blur', => @dispatchEvent(new FocusEvent('blur', bubbles: false))
|
||||
|
||||
unmountComponent: ->
|
||||
return unless @component?.isMounted()
|
||||
callRemoveHooks(this)
|
||||
React.unmountComponentAtNode(@rootElement)
|
||||
@component = null
|
||||
if @component?
|
||||
@component.destroy()
|
||||
@component.domNode.remove()
|
||||
@component = null
|
||||
|
||||
focused: ->
|
||||
@component?.focused()
|
||||
|
||||
blurred: (event) ->
|
||||
unless @useShadowDOM
|
||||
if event.relatedTarget is @component?.refs.input?.getDOMNode()
|
||||
if event.relatedTarget is @component.hiddenInputComponent.domNode
|
||||
event.stopImmediatePropagation()
|
||||
return
|
||||
|
||||
|
@ -14,7 +14,7 @@ class TextEditorPresenter
|
||||
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft} = params
|
||||
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
|
||||
{@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params
|
||||
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay} = params
|
||||
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
|
||||
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
|
||||
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
|
||||
|
||||
@ -56,7 +56,7 @@ class TextEditorPresenter
|
||||
@updateLinesState()
|
||||
@updateGutterState()
|
||||
@updateLineNumbersState()
|
||||
@disposables.add @model.onDidChangeGrammar(@updateContentState.bind(this))
|
||||
@disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this))
|
||||
@disposables.add @model.onDidChangePlaceholderText(@updateContentState.bind(this))
|
||||
@disposables.add @model.onDidChangeMini =>
|
||||
@updateScrollbarDimensions()
|
||||
@ -64,7 +64,10 @@ class TextEditorPresenter
|
||||
@updateContentState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateGutterState()
|
||||
@updateLineNumbersState()
|
||||
@disposables.add @model.onDidChangeGutterVisible =>
|
||||
@updateGutterState()
|
||||
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
|
||||
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
|
||||
@disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this))
|
||||
@ -73,19 +76,41 @@ class TextEditorPresenter
|
||||
@observeCursor(cursor) for cursor in @model.getCursors()
|
||||
|
||||
observeConfig: ->
|
||||
@scrollPastEnd = atom.config.get('editor.scrollPastEnd')
|
||||
configParams = {scope: @model.getRootScopeDescriptor()}
|
||||
|
||||
@disposables.add atom.config.onDidChange 'editor.showIndentGuide', scope: @model.getRootScopeDescriptor(), @updateContentState.bind(this)
|
||||
@disposables.add atom.config.onDidChange 'editor.scrollPastEnd', scope: @model.getRootScopeDescriptor(), ({newValue}) =>
|
||||
@scrollPastEnd = atom.config.get('editor.scrollPastEnd', configParams)
|
||||
@showLineNumbers = atom.config.get('editor.showLineNumbers', configParams)
|
||||
@showIndentGuide = atom.config.get('editor.showIndentGuide', configParams)
|
||||
|
||||
if @configDisposables?
|
||||
@configDisposables?.dispose()
|
||||
@disposables.remove(@configDisposables)
|
||||
|
||||
@configDisposables = new CompositeDisposable
|
||||
@disposables.add(@configDisposables)
|
||||
|
||||
@configDisposables.add atom.config.onDidChange 'editor.showIndentGuide', configParams, ({newValue}) =>
|
||||
@showIndentGuide = newValue
|
||||
@updateContentState()
|
||||
@configDisposables.add atom.config.onDidChange 'editor.scrollPastEnd', configParams, ({newValue}) =>
|
||||
@scrollPastEnd = newValue
|
||||
@updateScrollHeight()
|
||||
@updateVerticalScrollState()
|
||||
@updateScrollbarsState()
|
||||
@configDisposables.add atom.config.onDidChange 'editor.showLineNumbers', configParams, ({newValue}) =>
|
||||
@showLineNumbers = newValue
|
||||
@updateGutterState()
|
||||
|
||||
didChangeGrammar: ->
|
||||
@observeConfig()
|
||||
@updateContentState()
|
||||
@updateGutterState()
|
||||
|
||||
buildState: ->
|
||||
@state =
|
||||
horizontalScrollbar: {}
|
||||
verticalScrollbar: {}
|
||||
hiddenInput: {}
|
||||
content:
|
||||
scrollingVertically: false
|
||||
blinkCursorsOff: false
|
||||
@ -102,10 +127,12 @@ class TextEditorPresenter
|
||||
@updateStartRow()
|
||||
@updateEndRow()
|
||||
|
||||
@updateFocusedState()
|
||||
@updateHeightState()
|
||||
@updateVerticalScrollState()
|
||||
@updateHorizontalScrollState()
|
||||
@updateScrollbarsState()
|
||||
@updateHiddenInputState()
|
||||
@updateContentState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@ -114,6 +141,9 @@ class TextEditorPresenter
|
||||
@updateGutterState()
|
||||
@updateLineNumbersState()
|
||||
|
||||
updateFocusedState: ->
|
||||
@state.focused = @focused
|
||||
|
||||
updateHeightState: ->
|
||||
if @autoHeight
|
||||
@state.height = @contentHeight
|
||||
@ -153,10 +183,29 @@ class TextEditorPresenter
|
||||
|
||||
@emitter.emit 'did-update-state'
|
||||
|
||||
updateHiddenInputState: ->
|
||||
return unless lastCursor = @model.getLastCursor()
|
||||
|
||||
{top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange())
|
||||
|
||||
if @focused
|
||||
top -= @scrollTop
|
||||
left -= @scrollLeft
|
||||
@state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0)
|
||||
@state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0)
|
||||
else
|
||||
@state.hiddenInput.top = 0
|
||||
@state.hiddenInput.left = 0
|
||||
|
||||
@state.hiddenInput.height = height
|
||||
@state.hiddenInput.width = Math.max(width, 2)
|
||||
|
||||
@emitter.emit 'did-update-state'
|
||||
|
||||
updateContentState: ->
|
||||
@state.content.scrollWidth = @scrollWidth
|
||||
@state.content.scrollLeft = @scrollLeft
|
||||
@state.content.indentGuidesVisible = not @model.isMini() and atom.config.get('editor.showIndentGuide', scope: @model.getRootScopeDescriptor())
|
||||
@state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide
|
||||
@state.content.backgroundColor = if @model.isMini() then null else @backgroundColor
|
||||
@state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null
|
||||
@emitter.emit 'did-update-state'
|
||||
@ -244,6 +293,7 @@ class TextEditorPresenter
|
||||
@emitter.emit "did-update-state"
|
||||
|
||||
updateGutterState: ->
|
||||
@state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers
|
||||
@state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length
|
||||
@state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)"
|
||||
@gutterBackgroundColor
|
||||
@ -448,6 +498,12 @@ class TextEditorPresenter
|
||||
|
||||
getCursorBlinkResumeDelay: -> @cursorBlinkResumeDelay
|
||||
|
||||
setFocused: (focused) ->
|
||||
unless @focused is focused
|
||||
@focused = focused
|
||||
@updateFocusedState()
|
||||
@updateHiddenInputState()
|
||||
|
||||
setScrollTop: (scrollTop) ->
|
||||
scrollTop = @constrainScrollTop(scrollTop)
|
||||
|
||||
@ -458,6 +514,7 @@ class TextEditorPresenter
|
||||
@updateEndRow()
|
||||
@didStartScrolling()
|
||||
@updateVerticalScrollState()
|
||||
@updateHiddenInputState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateCursorsState()
|
||||
@ -487,6 +544,7 @@ class TextEditorPresenter
|
||||
@scrollLeft = scrollLeft
|
||||
@model.setScrollLeft(scrollLeft)
|
||||
@updateHorizontalScrollState()
|
||||
@updateHiddenInputState()
|
||||
@updateCursorsState() unless oldScrollLeft?
|
||||
|
||||
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
|
||||
@ -576,6 +634,7 @@ class TextEditorPresenter
|
||||
@updateHorizontalScrollState()
|
||||
@updateVerticalScrollState()
|
||||
@updateScrollbarsState()
|
||||
@updateHiddenInputState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateCursorsState()
|
||||
@ -623,6 +682,7 @@ class TextEditorPresenter
|
||||
@updateHorizontalScrollState()
|
||||
@updateVerticalScrollState()
|
||||
@updateScrollbarsState()
|
||||
@updateHiddenInputState()
|
||||
@updateContentState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@ -631,6 +691,7 @@ class TextEditorPresenter
|
||||
|
||||
clearScopedCharacterWidths: ->
|
||||
@characterWidthsByScope = {}
|
||||
@model.clearScopedCharWidths()
|
||||
|
||||
hasPixelPositionRequirements: ->
|
||||
@lineHeight? and @baseCharacterWidth?
|
||||
@ -885,6 +946,7 @@ class TextEditorPresenter
|
||||
|
||||
observeCursor: (cursor) ->
|
||||
didChangePositionDisposable = cursor.onDidChangePosition =>
|
||||
@updateHiddenInputState() if cursor.isLastCursor()
|
||||
@pauseCursorBlinking()
|
||||
@updateCursorsState()
|
||||
|
||||
@ -894,6 +956,7 @@ class TextEditorPresenter
|
||||
@disposables.remove(didChangePositionDisposable)
|
||||
@disposables.remove(didChangeVisibilityDisposable)
|
||||
@disposables.remove(didDestroyDisposable)
|
||||
@updateHiddenInputState()
|
||||
@updateCursorsState()
|
||||
|
||||
@disposables.add(didChangePositionDisposable)
|
||||
@ -902,6 +965,7 @@ class TextEditorPresenter
|
||||
|
||||
didAddCursor: (cursor) ->
|
||||
@observeCursor(cursor)
|
||||
@updateHiddenInputState()
|
||||
@pauseCursorBlinking()
|
||||
@updateCursorsState()
|
||||
|
||||
|
@ -126,7 +126,7 @@ class TextEditorView extends View
|
||||
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: -> document.activeElement is @element or document.activeElement is @element.component?.refs.input.getDOMNode()
|
||||
Object.defineProperty @::, 'isFocused', get: -> document.activeElement is @element or document.activeElement is @element.component?.hiddenInputComponent?.domNode
|
||||
Object.defineProperty @::, 'mini', get: -> @model?.isMini()
|
||||
Object.defineProperty @::, 'component', get: -> @element?.component
|
||||
|
||||
|
@ -42,9 +42,17 @@ Grim = require 'grim'
|
||||
# ```
|
||||
module.exports =
|
||||
class ViewRegistry
|
||||
documentPollingInterval: 200
|
||||
documentUpdateRequested: false
|
||||
performDocumentPollAfterUpdate: false
|
||||
pollIntervalHandle: null
|
||||
|
||||
constructor: ->
|
||||
@views = new WeakMap
|
||||
@providers = []
|
||||
@documentWriters = []
|
||||
@documentReaders = []
|
||||
@documentPollers = []
|
||||
|
||||
# Essential: Add a provider that will be used to construct views in the
|
||||
# workspace's view layer based on model objects in its model layer.
|
||||
@ -150,3 +158,53 @@ class ViewRegistry
|
||||
|
||||
findProvider: (object) ->
|
||||
find @providers, ({modelConstructor}) -> object instanceof modelConstructor
|
||||
|
||||
updateDocument: (fn) ->
|
||||
@documentWriters.push(fn)
|
||||
@requestDocumentUpdate()
|
||||
new Disposable =>
|
||||
@documentWriters = @documentWriters.filter (writer) -> writer isnt fn
|
||||
|
||||
readDocument: (fn) ->
|
||||
@documentReaders.push(fn)
|
||||
@requestDocumentUpdate()
|
||||
new Disposable =>
|
||||
@documentReaders = @documentReaders.filter (reader) -> reader isnt fn
|
||||
|
||||
pollDocument: (fn) ->
|
||||
@startPollingDocument() if @documentPollers.length is 0
|
||||
@documentPollers.push(fn)
|
||||
new Disposable =>
|
||||
@documentPollers = @documentPollers.filter (poller) -> poller isnt fn
|
||||
@stopPollingDocument() if @documentPollers.length is 0
|
||||
|
||||
clearDocumentRequests: ->
|
||||
@documentReaders = []
|
||||
@documentWriters = []
|
||||
@documentPollers = []
|
||||
@documentUpdateRequested = false
|
||||
|
||||
requestDocumentUpdate: ->
|
||||
unless @documentUpdateRequested
|
||||
@documentUpdateRequested = true
|
||||
requestAnimationFrame(@performDocumentUpdate)
|
||||
|
||||
performDocumentUpdate: =>
|
||||
@documentUpdateRequested = false
|
||||
writer() while writer = @documentWriters.shift()
|
||||
reader() while reader = @documentReaders.shift()
|
||||
@performDocumentPoll() if @performDocumentPollAfterUpdate
|
||||
|
||||
startPollingDocument: ->
|
||||
@pollIntervalHandle = window.setInterval(@performDocumentPoll, @documentPollingInterval)
|
||||
|
||||
stopPollingDocument: ->
|
||||
window.clearInterval(@pollIntervalHandle)
|
||||
|
||||
performDocumentPoll: =>
|
||||
if @documentUpdateRequested
|
||||
@performDocumentPollAfterUpdate = true
|
||||
else
|
||||
@performDocumentPollAfterUpdate = false
|
||||
poller() for poller in @documentPollers
|
||||
return
|
||||
|
Loading…
Reference in New Issue
Block a user