Comparar commits
148 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| d14d4aaae1 | |||
| 77599c799c | |||
| b94576dc09 | |||
| 9bdb961b3f | |||
| 59d96c9f1a | |||
| 6196882b9d | |||
| 3973939de1 | |||
| b792190693 | |||
| 1ae25ed85d | |||
| 5bb3095ffa | |||
| 3656d4cca6 | |||
| 3e6669cf3e | |||
| 6977660699 | |||
| ba6d11e24e | |||
| a685f3dc37 | |||
| 20a95269c9 | |||
| 78b8039384 | |||
| cd77870286 | |||
| 75652e36d9 | |||
| 9991cd73c7 | |||
| ac215e11b4 | |||
| 20ce35c017 | |||
| edd595a72f | |||
| 0a6116a785 | |||
| 2dc29a60ef | |||
| b78522d8e6 | |||
| c6f23c2edb | |||
| 342903567e | |||
| 648c58d41e | |||
| 7deb411e84 | |||
| cf50ec1861 | |||
| 4a74d4adba | |||
| f337553a70 | |||
| 0e85efdd28 | |||
| 3884a30f39 | |||
| f99b85a299 | |||
| de5c1fc28d | |||
| 89344c6cfd | |||
| 74e4756ef0 | |||
| 510520d2c7 | |||
| 4eb39b1be2 | |||
| 9d507ea692 | |||
| f005b2005f | |||
| b521e8dc97 | |||
| bbc1a264b5 | |||
| da4b3a47ef | |||
| 0e27bebbb3 | |||
| efdba9fc24 | |||
| 0910e86357 | |||
| 14776e3f0a | |||
| 42ab02d7d2 | |||
| 1b5be9aef8 | |||
| 9de8ab949f | |||
| 6108c04f40 | |||
| d8cafb1fc6 | |||
| 76241fb779 | |||
| d9a5d141eb | |||
| 37a040a620 | |||
| 35d3690088 | |||
| 578a76ba6b | |||
| da5ee3fa86 | |||
| a88486e950 | |||
| 1a22952eda | |||
| fd50a0db6c | |||
| ac25596002 | |||
| 9cc7ecb1be | |||
| 60fca8d8b2 | |||
| fd4f28911d | |||
| 1a61133def | |||
| f5fa3b837e | |||
| b21b9c3402 | |||
| d4517b1ab0 | |||
| 837b9eefbb | |||
| 71a27de7ac | |||
| 96ba4cc6bd | |||
| 9d7285d04a | |||
| e4c95d8ac3 | |||
| a5580a704d | |||
| c4b5a0f411 | |||
| af8c38ad80 | |||
| 0802b9bdd1 | |||
| b1fe567ce8 | |||
| 20838accc1 | |||
| 182531a010 | |||
| f218e985cf | |||
| 33081cefda | |||
| d26e8a2df1 | |||
| 1ff0b20cea | |||
| 66c35d6e3e | |||
| 5d2efc0469 | |||
| 970936f96d | |||
| 49bf3bb14e | |||
| 8ebd057b0c | |||
| 2f526c59c5 | |||
| 764139c25e | |||
| 4b0536ab6a | |||
| 500f992d32 | |||
| 3b93f3d71b | |||
| b412c2642d | |||
| 3c6c385ec8 | |||
| 06b5eba17c | |||
| 4ed07bb66d | |||
| c8b58761ba | |||
| b7b0ec067c | |||
| add3972477 | |||
| f479e9d029 | |||
| 78d87d8f7c | |||
| 2c5888e25a | |||
| de0b5c4c62 | |||
| 59b109654e | |||
| e9d6e36b6a | |||
| fe5ee524a8 | |||
| 9a496e62cb | |||
| a513cf260c | |||
| 62a1210604 | |||
| 773482467e | |||
| 5d8f831136 | |||
| 9c1efb6ba0 | |||
| 568b9f6999 | |||
| 06ef0792ce | |||
| bf9428aa19 | |||
| 64ef8add71 | |||
| 32a1854b7c | |||
| 590391a0ce | |||
| d0b52538b2 | |||
| 22942ae1bd | |||
| 0a9f7586ae | |||
| 3ec4b632ba | |||
| 115d764725 | |||
| 20bb14da81 | |||
| a3fb8b3aaa | |||
| 9e6aa8f873 | |||
| e2693da225 | |||
| 9a070e7f6d | |||
| db5059626f | |||
| 7095ccd32b | |||
| ac463143dd | |||
| 2296d2d378 | |||
| 143183aa25 | |||
| 0f4bcac8d4 | |||
| b09b54800f | |||
| 2120c3c298 | |||
| f4d8ef8315 | |||
| a1c2e1bb66 | |||
| 5a2bbc945b | |||
| 9c2ed478cd | |||
| 880e1ce1f0 | |||
| f0920bf63b |
@@ -135,7 +135,7 @@ describe "TextEditorComponent", ->
|
|||||||
expect(newLineHeightInPixels).not.toBe initialLineHeightInPixels
|
expect(newLineHeightInPixels).not.toBe initialLineHeightInPixels
|
||||||
expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * newLineHeightInPixels
|
expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * newLineHeightInPixels
|
||||||
|
|
||||||
it "updates the top position of lines when the font family changes", ->
|
xit "updates the top position of lines when the font family changes", ->
|
||||||
# Can't find a font that changes the line height, but we think one might exist
|
# Can't find a font that changes the line height, but we think one might exist
|
||||||
linesComponent = component.refs.lines
|
linesComponent = component.refs.lines
|
||||||
spyOn(linesComponent, 'measureLineHeightAndDefaultCharWidth').andCallFake -> editor.setLineHeightInPixels(10)
|
spyOn(linesComponent, 'measureLineHeightAndDefaultCharWidth').andCallFake -> editor.setLineHeightInPixels(10)
|
||||||
@@ -301,7 +301,9 @@ describe "TextEditorComponent", ->
|
|||||||
expect(component.lineNodeForScreenRow(10).textContent).toBe nbsp
|
expect(component.lineNodeForScreenRow(10).textContent).toBe nbsp
|
||||||
|
|
||||||
it "interleaves invisible line-ending characters with indent guides on empty lines", ->
|
it "interleaves invisible line-ending characters with indent guides on empty lines", ->
|
||||||
component.setShowIndentGuide(true)
|
atom.config.set "editor.showIndentGuide", true
|
||||||
|
nextAnimationFrame()
|
||||||
|
|
||||||
editor.setTextInBufferRange([[10, 0], [11, 0]], "\r\n", normalizeLineEndings: false)
|
editor.setTextInBufferRange([[10, 0], [11, 0]], "\r\n", normalizeLineEndings: false)
|
||||||
nextAnimationFrame()
|
nextAnimationFrame()
|
||||||
expect(component.lineNodeForScreenRow(10).innerHTML).toBe '<span class="indent-guide"><span class="invisible-character">C</span><span class="invisible-character">E</span></span>'
|
expect(component.lineNodeForScreenRow(10).innerHTML).toBe '<span class="indent-guide"><span class="invisible-character">C</span><span class="invisible-character">E</span></span>'
|
||||||
@@ -334,7 +336,8 @@ describe "TextEditorComponent", ->
|
|||||||
|
|
||||||
describe "when indent guides are enabled", ->
|
describe "when indent guides are enabled", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
component.setShowIndentGuide(true)
|
atom.config.set "editor.showIndentGuide", true
|
||||||
|
nextAnimationFrame()
|
||||||
|
|
||||||
it "adds an 'indent-guide' class to spans comprising the leading whitespace", ->
|
it "adds an 'indent-guide' class to spans comprising the leading whitespace", ->
|
||||||
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
|
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
|
||||||
@@ -426,7 +429,7 @@ describe "TextEditorComponent", ->
|
|||||||
|
|
||||||
describe "when indent guides are disabled", ->
|
describe "when indent guides are disabled", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
component.setShowIndentGuide(false)
|
expect(atom.config.get("editor.showIndentGuide")).toBe false
|
||||||
|
|
||||||
it "does not render indent guides on lines containing only whitespace", ->
|
it "does not render indent guides on lines containing only whitespace", ->
|
||||||
editor.getBuffer().insert([1, Infinity], '\n ')
|
editor.getBuffer().insert([1, Infinity], '\n ')
|
||||||
@@ -669,7 +672,7 @@ describe "TextEditorComponent", ->
|
|||||||
expect(lineNumberHasClass(1, 'folded')).toBe false
|
expect(lineNumberHasClass(1, 'folded')).toBe false
|
||||||
|
|
||||||
describe "cursor rendering", ->
|
describe "cursor rendering", ->
|
||||||
it "renders the currently visible cursors, translated relative to the scroll position", ->
|
it "renders the currently visible cursors", ->
|
||||||
cursor1 = editor.getLastCursor()
|
cursor1 = editor.getLastCursor()
|
||||||
cursor1.setScreenPosition([0, 5])
|
cursor1.setScreenPosition([0, 5])
|
||||||
|
|
||||||
@@ -706,9 +709,16 @@ describe "TextEditorComponent", ->
|
|||||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
|
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[1].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * 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(cursorMovedListener).toHaveBeenCalled()
|
||||||
|
|
||||||
cursor3.destroy()
|
cursor3.destroy()
|
||||||
nextAnimationFrame()
|
nextAnimationFrame()
|
||||||
cursorNodes = componentNode.querySelectorAll('.cursor')
|
cursorNodes = componentNode.querySelectorAll('.cursor')
|
||||||
|
|
||||||
expect(cursorNodes.length).toBe 1
|
expect(cursorNodes.length).toBe 1
|
||||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
|
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
|
||||||
|
|
||||||
@@ -789,18 +799,23 @@ describe "TextEditorComponent", ->
|
|||||||
cursorsNode = componentNode.querySelector('.cursors')
|
cursorsNode = componentNode.querySelector('.cursors')
|
||||||
|
|
||||||
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
||||||
|
|
||||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||||
|
nextAnimationFrame()
|
||||||
expect(cursorsNode.classList.contains('blink-off')).toBe true
|
expect(cursorsNode.classList.contains('blink-off')).toBe true
|
||||||
|
|
||||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||||
|
nextAnimationFrame()
|
||||||
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
||||||
|
|
||||||
# Stop blinking after moving the cursor
|
# Stop blinking after moving the cursor
|
||||||
editor.moveRight()
|
editor.moveRight()
|
||||||
|
nextAnimationFrame()
|
||||||
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
||||||
|
|
||||||
advanceClock(component.props.cursorBlinkResumeDelay)
|
advanceClock(component.props.cursorBlinkResumeDelay)
|
||||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||||
|
nextAnimationFrame()
|
||||||
expect(cursorsNode.classList.contains('blink-off')).toBe true
|
expect(cursorsNode.classList.contains('blink-off')).toBe true
|
||||||
|
|
||||||
it "does not render cursors that are associated with non-empty selections", ->
|
it "does not render cursors that are associated with non-empty selections", ->
|
||||||
@@ -931,7 +946,6 @@ describe "TextEditorComponent", ->
|
|||||||
it "will flash the selection when flash:true is passed to editor::setSelectedBufferRange", ->
|
it "will flash the selection when flash:true is passed to editor::setSelectedBufferRange", ->
|
||||||
editor.setSelectedBufferRange([[1, 6], [1, 10]], flash: true)
|
editor.setSelectedBufferRange([[1, 6], [1, 10]], flash: true)
|
||||||
nextAnimationFrame()
|
nextAnimationFrame()
|
||||||
nextAnimationFrame() # flash starts on its own frame
|
|
||||||
selectionNode = componentNode.querySelector('.selection')
|
selectionNode = componentNode.querySelector('.selection')
|
||||||
expect(selectionNode.classList.contains('flash')).toBe true
|
expect(selectionNode.classList.contains('flash')).toBe true
|
||||||
|
|
||||||
@@ -1105,14 +1119,14 @@ describe "TextEditorComponent", ->
|
|||||||
nextAnimationFrame()
|
nextAnimationFrame()
|
||||||
|
|
||||||
# Should not be rendering range containing the marker
|
# Should not be rendering range containing the marker
|
||||||
expect(component.getRenderedRowRange()[1]).toBeLessThan 9
|
expect(component.presenter.computeEndRow()).toBeLessThan 9
|
||||||
|
|
||||||
regions = componentNode.querySelectorAll('.some-highlight .region')
|
regions = componentNode.querySelectorAll('.some-highlight .region')
|
||||||
|
|
||||||
# Nothing when outside the rendered row range
|
# Nothing when outside the rendered row range
|
||||||
expect(regions.length).toBe 0
|
expect(regions.length).toBe 0
|
||||||
|
|
||||||
verticalScrollbarNode.scrollTop = 3.5 * lineHeightInPixels
|
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
|
||||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||||
nextAnimationFrame()
|
nextAnimationFrame()
|
||||||
|
|
||||||
@@ -1191,6 +1205,8 @@ describe "TextEditorComponent", ->
|
|||||||
advanceClock(2)
|
advanceClock(2)
|
||||||
|
|
||||||
decoration.flash('flash-class', 10)
|
decoration.flash('flash-class', 10)
|
||||||
|
nextAnimationFrame()
|
||||||
|
|
||||||
# Removed for 1 frame to force CSS transition to restart
|
# Removed for 1 frame to force CSS transition to restart
|
||||||
expect(highlightNode.classList.contains('flash-class')).toBe false
|
expect(highlightNode.classList.contains('flash-class')).toBe false
|
||||||
|
|
||||||
@@ -1968,14 +1984,14 @@ describe "TextEditorComponent", ->
|
|||||||
it "assigns the bottom/right of the scrollbars to the width of the opposite scrollbar if it is visible", ->
|
it "assigns the bottom/right of the scrollbars to the width of the opposite scrollbar if it is visible", ->
|
||||||
scrollbarCornerNode = componentNode.querySelector('.scrollbar-corner')
|
scrollbarCornerNode = componentNode.querySelector('.scrollbar-corner')
|
||||||
|
|
||||||
expect(verticalScrollbarNode.style.bottom).toBe ''
|
expect(verticalScrollbarNode.style.bottom).toBe '0px'
|
||||||
expect(horizontalScrollbarNode.style.right).toBe ''
|
expect(horizontalScrollbarNode.style.right).toBe '0px'
|
||||||
|
|
||||||
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
|
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||||
wrapperNode.style.width = '1000px'
|
wrapperNode.style.width = '1000px'
|
||||||
component.measureHeightAndWidth()
|
component.measureHeightAndWidth()
|
||||||
nextAnimationFrame()
|
nextAnimationFrame()
|
||||||
expect(verticalScrollbarNode.style.bottom).toBe ''
|
expect(verticalScrollbarNode.style.bottom).toBe '0px'
|
||||||
expect(horizontalScrollbarNode.style.right).toBe verticalScrollbarNode.offsetWidth + 'px'
|
expect(horizontalScrollbarNode.style.right).toBe verticalScrollbarNode.offsetWidth + 'px'
|
||||||
expect(scrollbarCornerNode.style.display).toBe 'none'
|
expect(scrollbarCornerNode.style.display).toBe 'none'
|
||||||
|
|
||||||
@@ -1990,7 +2006,7 @@ describe "TextEditorComponent", ->
|
|||||||
component.measureHeightAndWidth()
|
component.measureHeightAndWidth()
|
||||||
nextAnimationFrame()
|
nextAnimationFrame()
|
||||||
expect(verticalScrollbarNode.style.bottom).toBe horizontalScrollbarNode.offsetHeight + 'px'
|
expect(verticalScrollbarNode.style.bottom).toBe horizontalScrollbarNode.offsetHeight + 'px'
|
||||||
expect(horizontalScrollbarNode.style.right).toBe ''
|
expect(horizontalScrollbarNode.style.right).toBe '0px'
|
||||||
expect(scrollbarCornerNode.style.display).toBe 'none'
|
expect(scrollbarCornerNode.style.display).toBe 'none'
|
||||||
|
|
||||||
it "accounts for the width of the gutter in the scrollWidth of the horizontal scrollbar", ->
|
it "accounts for the width of the gutter in the scrollWidth of the horizontal scrollbar", ->
|
||||||
@@ -2075,7 +2091,7 @@ describe "TextEditorComponent", ->
|
|||||||
componentNode.dispatchEvent(wheelEvent)
|
componentNode.dispatchEvent(wheelEvent)
|
||||||
nextAnimationFrame()
|
nextAnimationFrame()
|
||||||
|
|
||||||
expect(component.mouseWheelScreenRow).toBe null
|
expect(component.presenter.mouseWheelScreenRow).toBe null
|
||||||
|
|
||||||
it "clears the mouseWheelScreenRow after a delay even if the event does not cause scrolling", ->
|
it "clears the mouseWheelScreenRow after a delay even if the event does not cause scrolling", ->
|
||||||
expect(editor.getScrollTop()).toBe 0
|
expect(editor.getScrollTop()).toBe 0
|
||||||
@@ -2084,13 +2100,12 @@ describe "TextEditorComponent", ->
|
|||||||
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 10)
|
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 10)
|
||||||
Object.defineProperty(wheelEvent, 'target', get: -> lineNode)
|
Object.defineProperty(wheelEvent, 'target', get: -> lineNode)
|
||||||
componentNode.dispatchEvent(wheelEvent)
|
componentNode.dispatchEvent(wheelEvent)
|
||||||
expect(nextAnimationFrame).toBe noAnimationFrame
|
|
||||||
|
|
||||||
expect(editor.getScrollTop()).toBe 0
|
expect(editor.getScrollTop()).toBe 0
|
||||||
|
|
||||||
expect(component.mouseWheelScreenRow).toBe 0
|
expect(component.presenter.mouseWheelScreenRow).toBe 0
|
||||||
advanceClock(component.mouseWheelScreenRowClearDelay)
|
advanceClock(component.presenter.stoppedScrollingDelay)
|
||||||
expect(component.mouseWheelScreenRow).toBe null
|
expect(component.presenter.mouseWheelScreenRow).toBe null
|
||||||
|
|
||||||
it "does not preserve the line if it is on screen", ->
|
it "does not preserve the line if it is on screen", ->
|
||||||
expect(componentNode.querySelectorAll('.line-number').length).toBe 14 # dummy line
|
expect(componentNode.querySelectorAll('.line-number').length).toBe 14 # dummy line
|
||||||
@@ -2101,9 +2116,8 @@ describe "TextEditorComponent", ->
|
|||||||
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 100) # goes nowhere, we're already at scrollTop 0
|
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 100) # goes nowhere, we're already at scrollTop 0
|
||||||
Object.defineProperty(wheelEvent, 'target', get: -> lineNode)
|
Object.defineProperty(wheelEvent, 'target', get: -> lineNode)
|
||||||
componentNode.dispatchEvent(wheelEvent)
|
componentNode.dispatchEvent(wheelEvent)
|
||||||
expect(nextAnimationFrame).toBe noAnimationFrame
|
|
||||||
|
|
||||||
expect(component.mouseWheelScreenRow).toBe 0
|
expect(component.presenter.mouseWheelScreenRow).toBe 0
|
||||||
editor.insertText("hello")
|
editor.insertText("hello")
|
||||||
expect(componentNode.querySelectorAll('.line-number').length).toBe 14 # dummy line
|
expect(componentNode.querySelectorAll('.line-number').length).toBe 14 # dummy line
|
||||||
expect(componentNode.querySelectorAll('.line').length).toBe 13
|
expect(componentNode.querySelectorAll('.line').length).toBe 13
|
||||||
@@ -2524,6 +2538,7 @@ describe "TextEditorComponent", ->
|
|||||||
it "does not assign a height on the component node", ->
|
it "does not assign a height on the component node", ->
|
||||||
wrapperNode.style.height = '200px'
|
wrapperNode.style.height = '200px'
|
||||||
component.measureHeightAndWidth()
|
component.measureHeightAndWidth()
|
||||||
|
nextAnimationFrame()
|
||||||
expect(componentNode.style.height).toBe ''
|
expect(componentNode.style.height).toBe ''
|
||||||
|
|
||||||
describe "when the wrapper view does not have an explicit height", ->
|
describe "when the wrapper view does not have an explicit height", ->
|
||||||
@@ -2581,6 +2596,7 @@ describe "TextEditorComponent", ->
|
|||||||
|
|
||||||
it "works with the ::setEditorHeightInLines and ::setEditorWidthInChars helpers", ->
|
it "works with the ::setEditorHeightInLines and ::setEditorWidthInChars helpers", ->
|
||||||
setEditorHeightInLines(wrapperView, 7)
|
setEditorHeightInLines(wrapperView, 7)
|
||||||
|
nextAnimationFrame()
|
||||||
expect(componentNode.offsetHeight).toBe lineHeightInPixels * 7
|
expect(componentNode.offsetHeight).toBe lineHeightInPixels * 7
|
||||||
|
|
||||||
setEditorWidthInChars(wrapperView, 10)
|
setEditorWidthInChars(wrapperView, 10)
|
||||||
@@ -2714,6 +2730,7 @@ describe "TextEditorComponent", ->
|
|||||||
beforeEach ->
|
beforeEach ->
|
||||||
atom.config.set 'editor.showIndentGuide', true, scopeSelector: '.source.js'
|
atom.config.set 'editor.showIndentGuide', true, scopeSelector: '.source.js'
|
||||||
atom.config.set 'editor.showIndentGuide', false, scopeSelector: '.source.coffee'
|
atom.config.set 'editor.showIndentGuide', false, scopeSelector: '.source.coffee'
|
||||||
|
nextAnimationFrame()
|
||||||
|
|
||||||
it "has an 'indent-guide' class when scoped editor.showIndentGuide is true, but not when scoped editor.showIndentGuide is false", ->
|
it "has an 'indent-guide' class when scoped editor.showIndentGuide is true, but not when scoped editor.showIndentGuide is false", ->
|
||||||
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
|
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
|
||||||
@@ -2722,6 +2739,7 @@ describe "TextEditorComponent", ->
|
|||||||
expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false
|
expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false
|
||||||
|
|
||||||
editor.setGrammar(coffeeEditor.getGrammar())
|
editor.setGrammar(coffeeEditor.getGrammar())
|
||||||
|
nextAnimationFrame()
|
||||||
|
|
||||||
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
|
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
|
||||||
expect(line1LeafNodes[0].textContent).toBe ' '
|
expect(line1LeafNodes[0].textContent).toBe ' '
|
||||||
@@ -2735,6 +2753,7 @@ describe "TextEditorComponent", ->
|
|||||||
expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false
|
expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false
|
||||||
|
|
||||||
atom.config.set 'editor.showIndentGuide', false, scopeSelector: '.source.js'
|
atom.config.set 'editor.showIndentGuide', false, scopeSelector: '.source.js'
|
||||||
|
nextAnimationFrame()
|
||||||
|
|
||||||
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
|
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
|
||||||
expect(line1LeafNodes[0].textContent).toBe ' '
|
expect(line1LeafNodes[0].textContent).toBe ' '
|
||||||
|
|||||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -7,12 +7,8 @@ CursorComponent = React.createClass
|
|||||||
displayName: 'CursorComponent'
|
displayName: 'CursorComponent'
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
{pixelRect, defaultCharWidth} = @props
|
{pixelRect} = @props
|
||||||
{top, left, height, width} = pixelRect
|
{top, left, height, width} = pixelRect
|
||||||
width = defaultCharWidth if width is 0
|
|
||||||
WebkitTransform = "translate(#{left}px, #{top}px)"
|
WebkitTransform = "translate(#{left}px, #{top}px)"
|
||||||
|
|
||||||
div className: 'cursor', style: {height, width, WebkitTransform}
|
div className: 'cursor', style: {height, width, WebkitTransform}
|
||||||
|
|
||||||
shouldComponentUpdate: (newProps) ->
|
|
||||||
not isEqualForProperties(newProps, @props, 'pixelRect', 'defaultCharWidth')
|
|
||||||
|
|||||||
@@ -7,55 +7,14 @@ CursorComponent = require './cursor-component'
|
|||||||
module.exports =
|
module.exports =
|
||||||
CursorsComponent = React.createClass
|
CursorsComponent = React.createClass
|
||||||
displayName: 'CursorsComponent'
|
displayName: 'CursorsComponent'
|
||||||
mixins: [SubscriberMixin]
|
|
||||||
|
|
||||||
cursorBlinkIntervalHandle: null
|
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
{performedInitialMeasurement, cursorPixelRects, defaultCharWidth} = @props
|
{presenter} = @props
|
||||||
{blinkOff} = @state
|
|
||||||
|
|
||||||
className = 'cursors'
|
className = 'cursors'
|
||||||
className += ' blink-off' if blinkOff
|
className += ' blink-off' if presenter.state.content.blinkCursorsOff
|
||||||
|
|
||||||
div {className},
|
div {className},
|
||||||
if performedInitialMeasurement
|
if presenter.hasRequiredMeasurements()
|
||||||
for key, pixelRect of cursorPixelRects
|
for key, pixelRect of presenter.state.content.cursors
|
||||||
CursorComponent({key, pixelRect, defaultCharWidth})
|
CursorComponent({key, pixelRect})
|
||||||
|
|
||||||
getInitialState: ->
|
|
||||||
blinkOff: false
|
|
||||||
|
|
||||||
componentDidMount: ->
|
|
||||||
@startBlinkingCursors()
|
|
||||||
|
|
||||||
componentWillUnmount: ->
|
|
||||||
@stopBlinkingCursors()
|
|
||||||
|
|
||||||
shouldComponentUpdate: (newProps, newState) ->
|
|
||||||
not newState.blinkOff is @state.blinkOff or
|
|
||||||
not isEqualForProperties(newProps, @props, 'cursorPixelRects', 'scrollTop', 'scrollLeft', 'defaultCharWidth', 'useHardwareAcceleration')
|
|
||||||
|
|
||||||
componentWillUpdate: (newProps) ->
|
|
||||||
cursorsMoved = @props.cursorPixelRects? and
|
|
||||||
isEqualForProperties(newProps, @props, 'defaultCharWidth', 'scopedCharacterWidthsChangeCount') and
|
|
||||||
not isEqual(newProps.cursorPixelRects, @props.cursorPixelRects)
|
|
||||||
|
|
||||||
@pauseCursorBlinking() if cursorsMoved
|
|
||||||
|
|
||||||
startBlinkingCursors: ->
|
|
||||||
@toggleCursorBlinkHandle = setInterval(@toggleCursorBlink, @props.cursorBlinkPeriod / 2) if @isMounted()
|
|
||||||
|
|
||||||
startBlinkingCursorsAfterDelay: null # Created lazily
|
|
||||||
|
|
||||||
stopBlinkingCursors: ->
|
|
||||||
clearInterval(@toggleCursorBlinkHandle)
|
|
||||||
|
|
||||||
toggleCursorBlink: ->
|
|
||||||
@setState(blinkOff: not @state.blinkOff)
|
|
||||||
|
|
||||||
pauseCursorBlinking: ->
|
|
||||||
@state.blinkOff = false
|
|
||||||
@stopBlinkingCursors()
|
|
||||||
@startBlinkingCursorsAfterDelay ?= debounce(@startBlinkingCursors, @props.cursorBlinkResumeDelay)
|
|
||||||
@startBlinkingCursorsAfterDelay()
|
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ class Decoration
|
|||||||
@emitter.emit 'did-destroy'
|
@emitter.emit 'did-destroy'
|
||||||
@emitter.dispose()
|
@emitter.dispose()
|
||||||
|
|
||||||
|
isDestroyed: -> @destroyed
|
||||||
|
|
||||||
###
|
###
|
||||||
Section: Event Subscription
|
Section: Event Subscription
|
||||||
###
|
###
|
||||||
|
|||||||
@@ -617,10 +617,10 @@ class DisplayBuffer extends Model
|
|||||||
# bufferRange - The {Range} to convert
|
# bufferRange - The {Range} to convert
|
||||||
#
|
#
|
||||||
# Returns a {Range}.
|
# Returns a {Range}.
|
||||||
screenRangeForBufferRange: (bufferRange) ->
|
screenRangeForBufferRange: (bufferRange, options) ->
|
||||||
bufferRange = Range.fromObject(bufferRange)
|
bufferRange = Range.fromObject(bufferRange)
|
||||||
start = @screenPositionForBufferPosition(bufferRange.start)
|
start = @screenPositionForBufferPosition(bufferRange.start, options)
|
||||||
end = @screenPositionForBufferPosition(bufferRange.end)
|
end = @screenPositionForBufferPosition(bufferRange.end, options)
|
||||||
new Range(start, end)
|
new Range(start, end)
|
||||||
|
|
||||||
# Given a screen range, this converts it into a buffer position.
|
# Given a screen range, this converts it into a buffer position.
|
||||||
@@ -897,7 +897,7 @@ class DisplayBuffer extends Model
|
|||||||
getLineDecorations: (propertyFilter) ->
|
getLineDecorations: (propertyFilter) ->
|
||||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line')
|
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line')
|
||||||
|
|
||||||
getGutterDecorations: (propertyFilter) ->
|
getLineNumberDecorations: (propertyFilter) ->
|
||||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line-number')
|
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line-number')
|
||||||
|
|
||||||
getHighlightDecorations: (propertyFilter) ->
|
getHighlightDecorations: (propertyFilter) ->
|
||||||
|
|||||||
+61
-129
@@ -1,7 +1,8 @@
|
|||||||
_ = require 'underscore-plus'
|
_ = require 'underscore-plus'
|
||||||
React = require 'react-atom-fork'
|
React = require 'react-atom-fork'
|
||||||
{div} = require 'reactionary-atom-fork'
|
{div} = require 'reactionary-atom-fork'
|
||||||
{isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
_ = require 'underscore-plus'
|
||||||
|
{isEqual, isEqualForProperties, multiplyString, toArray} = _
|
||||||
Decoration = require './decoration'
|
Decoration = require './decoration'
|
||||||
SubscriberMixin = require './subscriber-mixin'
|
SubscriberMixin = require './subscriber-mixin'
|
||||||
|
|
||||||
@@ -12,23 +13,26 @@ GutterComponent = React.createClass
|
|||||||
displayName: 'GutterComponent'
|
displayName: 'GutterComponent'
|
||||||
mixins: [SubscriberMixin]
|
mixins: [SubscriberMixin]
|
||||||
|
|
||||||
|
maxLineNumberDigits: null
|
||||||
dummyLineNumberNode: null
|
dummyLineNumberNode: null
|
||||||
measuredWidth: null
|
measuredWidth: null
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
{scrollHeight, scrollViewHeight, backgroundColor, gutterBackgroundColor} = @props
|
{presenter} = @props
|
||||||
|
@newState = presenter.state.gutter
|
||||||
|
@oldState ?= {lineNumbers: {}}
|
||||||
|
|
||||||
if gutterBackgroundColor isnt 'rbga(0, 0, 0, 0)'
|
{scrollHeight, backgroundColor} = @newState
|
||||||
backgroundColor = gutterBackgroundColor
|
|
||||||
|
|
||||||
div className: 'gutter',
|
div className: 'gutter',
|
||||||
div className: 'line-numbers', ref: 'lineNumbers', style:
|
div className: 'line-numbers', ref: 'lineNumbers', style:
|
||||||
height: Math.max(scrollHeight, scrollViewHeight)
|
height: scrollHeight
|
||||||
WebkitTransform: @getTransform()
|
WebkitTransform: @getTransform() if presenter.hasRequiredMeasurements()
|
||||||
backgroundColor: backgroundColor
|
backgroundColor: backgroundColor
|
||||||
|
|
||||||
getTransform: ->
|
getTransform: ->
|
||||||
{scrollTop, useHardwareAcceleration} = @props
|
{useHardwareAcceleration} = @props
|
||||||
|
{scrollTop} = @newState
|
||||||
|
|
||||||
if useHardwareAcceleration
|
if useHardwareAcceleration
|
||||||
"translate3d(0px, #{-scrollTop}px, 0px)"
|
"translate3d(0px, #{-scrollTop}px, 0px)"
|
||||||
@@ -37,141 +41,81 @@ GutterComponent = React.createClass
|
|||||||
|
|
||||||
componentWillMount: ->
|
componentWillMount: ->
|
||||||
@lineNumberNodesById = {}
|
@lineNumberNodesById = {}
|
||||||
@lineNumberIdsByScreenRow = {}
|
|
||||||
@screenRowsByLineNumberId = {}
|
|
||||||
@renderedDecorationsByLineNumberId = {}
|
|
||||||
|
|
||||||
componentDidMount: ->
|
componentDidMount: ->
|
||||||
|
{@maxLineNumberDigits} = @newState
|
||||||
@appendDummyLineNumber()
|
@appendDummyLineNumber()
|
||||||
@updateLineNumbers() if @props.performedInitialMeasurement
|
@updateLineNumbers()
|
||||||
|
|
||||||
node = @getDOMNode()
|
node = @getDOMNode()
|
||||||
node.addEventListener 'click', @onClick
|
node.addEventListener 'click', @onClick
|
||||||
node.addEventListener 'mousedown', @onMouseDown
|
node.addEventListener 'mousedown', @onMouseDown
|
||||||
|
|
||||||
# Only update the gutter if the visible row range has changed or if a
|
|
||||||
# non-zero-delta change to the screen lines has occurred within the current
|
|
||||||
# visible row range.
|
|
||||||
shouldComponentUpdate: (newProps) ->
|
|
||||||
return true unless isEqualForProperties(newProps, @props,
|
|
||||||
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow', 'lineDecorations',
|
|
||||||
'scrollViewHeight', 'useHardwareAcceleration', 'backgroundColor', 'gutterBackgroundColor'
|
|
||||||
)
|
|
||||||
|
|
||||||
{renderedRowRange, pendingChanges, lineDecorations} = newProps
|
|
||||||
return false unless renderedRowRange?
|
|
||||||
|
|
||||||
for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0
|
|
||||||
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
|
|
||||||
|
|
||||||
false
|
|
||||||
|
|
||||||
componentDidUpdate: (oldProps) ->
|
componentDidUpdate: (oldProps) ->
|
||||||
return unless @props.performedInitialMeasurement
|
{maxLineNumberDigits} = @newState
|
||||||
|
unless maxLineNumberDigits is @maxLineNumberDigits
|
||||||
unless isEqualForProperties(oldProps, @props, 'maxLineNumberDigits')
|
@maxLineNumberDigits = maxLineNumberDigits
|
||||||
@updateDummyLineNumber()
|
@updateDummyLineNumber()
|
||||||
@removeLineNumberNodes()
|
node.remove() for id, node of @lineNumberNodesById
|
||||||
|
@oldState = {lineNumbers: {}}
|
||||||
|
@lineNumberNodesById = {}
|
||||||
|
|
||||||
@clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels
|
|
||||||
@updateLineNumbers()
|
@updateLineNumbers()
|
||||||
|
|
||||||
clearScreenRowCaches: ->
|
|
||||||
@lineNumberIdsByScreenRow = {}
|
|
||||||
@screenRowsByLineNumberId = {}
|
|
||||||
|
|
||||||
# This dummy line number element holds the gutter to the appropriate width,
|
# This dummy line number element holds the gutter to the appropriate width,
|
||||||
# since the real line numbers are absolutely positioned for performance reasons.
|
# since the real line numbers are absolutely positioned for performance reasons.
|
||||||
appendDummyLineNumber: ->
|
appendDummyLineNumber: ->
|
||||||
{maxLineNumberDigits} = @props
|
WrapperDiv.innerHTML = @buildLineNumberHTML({bufferRow: -1})
|
||||||
WrapperDiv.innerHTML = @buildLineNumberHTML(-1, false, maxLineNumberDigits)
|
|
||||||
@dummyLineNumberNode = WrapperDiv.children[0]
|
@dummyLineNumberNode = WrapperDiv.children[0]
|
||||||
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
|
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
|
||||||
|
|
||||||
updateDummyLineNumber: ->
|
updateDummyLineNumber: ->
|
||||||
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false, @props.maxLineNumberDigits)
|
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false)
|
||||||
|
|
||||||
updateLineNumbers: ->
|
updateLineNumbers: ->
|
||||||
lineNumberIdsToPreserve = @appendOrUpdateVisibleLineNumberNodes()
|
|
||||||
@removeLineNumberNodes(lineNumberIdsToPreserve)
|
|
||||||
|
|
||||||
appendOrUpdateVisibleLineNumberNodes: ->
|
|
||||||
{editor, renderedRowRange, scrollTop, maxLineNumberDigits, lineDecorations} = @props
|
|
||||||
[startRow, endRow] = renderedRowRange
|
|
||||||
|
|
||||||
newLineNumberIds = null
|
newLineNumberIds = null
|
||||||
newLineNumbersHTML = null
|
newLineNumbersHTML = null
|
||||||
visibleLineNumberIds = new Set
|
|
||||||
|
|
||||||
wrapCount = 0
|
for id, lineNumberState of @newState.lineNumbers
|
||||||
for bufferRow, index in editor.bufferRowsForScreenRows(startRow, endRow - 1)
|
if @oldState.lineNumbers.hasOwnProperty(id)
|
||||||
screenRow = startRow + index
|
@updateLineNumberNode(id, lineNumberState)
|
||||||
|
|
||||||
if bufferRow is lastBufferRow
|
|
||||||
id = "#{bufferRow}-#{wrapCount++}"
|
|
||||||
else
|
|
||||||
id = bufferRow.toString()
|
|
||||||
lastBufferRow = bufferRow
|
|
||||||
wrapCount = 0
|
|
||||||
|
|
||||||
visibleLineNumberIds.add(id)
|
|
||||||
|
|
||||||
if @hasLineNumberNode(id)
|
|
||||||
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0)
|
|
||||||
else
|
else
|
||||||
newLineNumberIds ?= []
|
newLineNumberIds ?= []
|
||||||
newLineNumbersHTML ?= ""
|
newLineNumbersHTML ?= ""
|
||||||
newLineNumberIds.push(id)
|
newLineNumberIds.push(id)
|
||||||
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
|
newLineNumbersHTML += @buildLineNumberHTML(lineNumberState)
|
||||||
@screenRowsByLineNumberId[id] = screenRow
|
@oldState.lineNumbers[id] = _.clone(lineNumberState)
|
||||||
@lineNumberIdsByScreenRow[screenRow] = id
|
|
||||||
|
|
||||||
@renderedDecorationsByLineNumberId[id] = lineDecorations[screenRow]
|
|
||||||
|
|
||||||
if newLineNumberIds?
|
if newLineNumberIds?
|
||||||
WrapperDiv.innerHTML = newLineNumbersHTML
|
WrapperDiv.innerHTML = newLineNumbersHTML
|
||||||
newLineNumberNodes = toArray(WrapperDiv.children)
|
newLineNumberNodes = toArray(WrapperDiv.children)
|
||||||
|
|
||||||
node = @refs.lineNumbers.getDOMNode()
|
node = @refs.lineNumbers.getDOMNode()
|
||||||
for lineNumberId, i in newLineNumberIds
|
for id, i in newLineNumberIds
|
||||||
lineNumberNode = newLineNumberNodes[i]
|
lineNumberNode = newLineNumberNodes[i]
|
||||||
@lineNumberNodesById[lineNumberId] = lineNumberNode
|
@lineNumberNodesById[id] = lineNumberNode
|
||||||
node.appendChild(lineNumberNode)
|
node.appendChild(lineNumberNode)
|
||||||
|
|
||||||
visibleLineNumberIds
|
for id, lineNumberState of @oldState.lineNumbers
|
||||||
|
unless @newState.lineNumbers.hasOwnProperty(id)
|
||||||
|
@lineNumberNodesById[id].remove()
|
||||||
|
delete @lineNumberNodesById[id]
|
||||||
|
delete @oldState.lineNumbers[id]
|
||||||
|
|
||||||
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
|
buildLineNumberHTML: (lineNumberState) ->
|
||||||
{mouseWheelScreenRow} = @props
|
{screenRow, bufferRow, softWrapped, top, decorationClasses} = lineNumberState
|
||||||
node = @refs.lineNumbers.getDOMNode()
|
|
||||||
for lineNumberId, lineNumberNode of @lineNumberNodesById when not lineNumberIdsToPreserve?.has(lineNumberId)
|
|
||||||
screenRow = @screenRowsByLineNumberId[lineNumberId]
|
|
||||||
if not screenRow? or screenRow isnt mouseWheelScreenRow
|
|
||||||
delete @lineNumberNodesById[lineNumberId]
|
|
||||||
delete @lineNumberIdsByScreenRow[screenRow] if @lineNumberIdsByScreenRow[screenRow] is lineNumberId
|
|
||||||
delete @screenRowsByLineNumberId[lineNumberId]
|
|
||||||
delete @renderedDecorationsByLineNumberId[lineNumberId]
|
|
||||||
node.removeChild(lineNumberNode)
|
|
||||||
|
|
||||||
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
|
|
||||||
{editor, lineHeightInPixels, lineDecorations} = @props
|
|
||||||
if screenRow?
|
if screenRow?
|
||||||
style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
|
style = "position: absolute; top: #{top}px;"
|
||||||
else
|
else
|
||||||
style = "visibility: hidden;"
|
style = "visibility: hidden;"
|
||||||
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
|
className = @buildLineNumberClassName(lineNumberState)
|
||||||
|
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped)
|
||||||
|
|
||||||
classes = ''
|
"<div class=\"#{className}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||||
if lineDecorations? and decorations = lineDecorations[screenRow]
|
|
||||||
for id, decoration of decorations
|
|
||||||
if Decoration.isType(decoration, 'line-number')
|
|
||||||
classes += decoration.class + ' '
|
|
||||||
|
|
||||||
classes += "foldable " if bufferRow >= 0 and editor.isFoldableAtBufferRow(bufferRow)
|
buildLineNumberInnerHTML: (bufferRow, softWrapped) ->
|
||||||
classes += "line-number line-number-#{bufferRow}"
|
{maxLineNumberDigits} = @newState
|
||||||
|
|
||||||
"<div class=\"#{classes}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
|
||||||
|
|
||||||
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
|
|
||||||
if softWrapped
|
if softWrapped
|
||||||
lineNumber = "•"
|
lineNumber = "•"
|
||||||
else
|
else
|
||||||
@@ -181,46 +125,34 @@ GutterComponent = React.createClass
|
|||||||
iconHTML = '<div class="icon-right"></div>'
|
iconHTML = '<div class="icon-right"></div>'
|
||||||
padding + lineNumber + iconHTML
|
padding + lineNumber + iconHTML
|
||||||
|
|
||||||
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped) ->
|
updateLineNumberNode: (lineNumberId, newLineNumberState) ->
|
||||||
{editor, lineDecorations} = @props
|
oldLineNumberState = @oldState.lineNumbers[lineNumberId]
|
||||||
node = @lineNumberNodesById[lineNumberId]
|
node = @lineNumberNodesById[lineNumberId]
|
||||||
|
|
||||||
if editor.isFoldableAtBufferRow(bufferRow)
|
unless oldLineNumberState.foldable is newLineNumberState.foldable and _.isEqual(oldLineNumberState.decorationClasses, newLineNumberState.decorationClasses)
|
||||||
node.classList.add('foldable')
|
node.className = @buildLineNumberClassName(newLineNumberState)
|
||||||
else
|
oldLineNumberState.foldable = newLineNumberState.foldable
|
||||||
node.classList.remove('foldable')
|
oldLineNumberState.decorationClasses = _.clone(newLineNumberState.decorationClasses)
|
||||||
|
|
||||||
decorations = lineDecorations[screenRow]
|
unless oldLineNumberState.top is newLineNumberState.top
|
||||||
previousDecorations = @renderedDecorationsByLineNumberId[lineNumberId]
|
node.style.top = newLineNumberState.top + 'px'
|
||||||
|
node.dataset.screenRow = newLineNumberState.screenRow
|
||||||
|
oldLineNumberState.top = newLineNumberState.top
|
||||||
|
oldLineNumberState.screenRow = newLineNumberState.screenRow
|
||||||
|
|
||||||
if previousDecorations?
|
buildLineNumberClassName: ({bufferRow, foldable, decorationClasses}) ->
|
||||||
for id, decoration of previousDecorations
|
className = "line-number line-number-#{bufferRow}"
|
||||||
if Decoration.isType(decoration, 'line-number') and not @hasDecoration(decorations, decoration)
|
className += " " + decorationClasses.join(' ') if decorationClasses?
|
||||||
node.classList.remove(decoration.class)
|
className += " foldable" if foldable
|
||||||
|
className
|
||||||
if decorations?
|
|
||||||
for id, decoration of decorations
|
|
||||||
if Decoration.isType(decoration, 'line-number') and not @hasDecoration(previousDecorations, decoration)
|
|
||||||
node.classList.add(decoration.class)
|
|
||||||
|
|
||||||
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
|
|
||||||
{lineHeightInPixels} = @props
|
|
||||||
node.style.top = screenRow * lineHeightInPixels + 'px'
|
|
||||||
node.dataset.screenRow = screenRow
|
|
||||||
@screenRowsByLineNumberId[lineNumberId] = screenRow
|
|
||||||
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
|
|
||||||
|
|
||||||
hasDecoration: (decorations, decoration) ->
|
|
||||||
decorations? and decorations[decoration.id] is decoration
|
|
||||||
|
|
||||||
hasLineNumberNode: (lineNumberId) ->
|
|
||||||
@lineNumberNodesById.hasOwnProperty(lineNumberId)
|
|
||||||
|
|
||||||
lineNumberNodeForScreenRow: (screenRow) ->
|
lineNumberNodeForScreenRow: (screenRow) ->
|
||||||
@lineNumberNodesById[@lineNumberIdsByScreenRow[screenRow]]
|
for id, lineNumberState of @oldState.lineNumbers
|
||||||
|
if lineNumberState.screenRow is screenRow
|
||||||
|
return @lineNumberNodesById[id]
|
||||||
|
null
|
||||||
|
|
||||||
onMouseDown: (event) ->
|
onMouseDown: (event) ->
|
||||||
{editor} = @props
|
|
||||||
{target} = event
|
{target} = event
|
||||||
lineNumber = target.parentNode
|
lineNumber = target.parentNode
|
||||||
|
|
||||||
|
|||||||
@@ -5,94 +5,46 @@ React = require 'react-atom-fork'
|
|||||||
module.exports =
|
module.exports =
|
||||||
HighlightComponent = React.createClass
|
HighlightComponent = React.createClass
|
||||||
displayName: 'HighlightComponent'
|
displayName: 'HighlightComponent'
|
||||||
|
currentFlashCount: 0
|
||||||
|
currentFlashClass: null
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
{startPixelPosition, endPixelPosition, decoration} = @props
|
{state} = @props
|
||||||
|
|
||||||
className = 'highlight'
|
className = 'highlight'
|
||||||
className += " #{decoration.class}" if decoration.class?
|
className += " #{state.class}" if state.class?
|
||||||
|
|
||||||
div {className},
|
div {className},
|
||||||
if endPixelPosition.top is startPixelPosition.top
|
for region, i in state.regions
|
||||||
@renderSingleLineRegions(decoration.deprecatedRegionClass)
|
regionClassName = 'region'
|
||||||
else
|
regionClassName += " #{state.deprecatedRegionClass}" if state.deprecatedRegionClass?
|
||||||
@renderMultiLineRegions(decoration.deprecatedRegionClass)
|
div className: regionClassName, key: i, style: region
|
||||||
|
|
||||||
componentDidMount: ->
|
componentDidMount: ->
|
||||||
{editor, decoration} = @props
|
@flashIfRequested()
|
||||||
if decoration.id?
|
|
||||||
@decoration = editor.decorationForId(decoration.id)
|
|
||||||
@decorationDisposable = @decoration.onDidFlash @startFlashAnimation
|
|
||||||
@startFlashAnimation()
|
|
||||||
|
|
||||||
componentWillUnmount: ->
|
componentDidUpdate: ->
|
||||||
@decorationDisposable?.dispose()
|
@flashIfRequested()
|
||||||
@decorationDisposable = null
|
|
||||||
|
|
||||||
startFlashAnimation: ->
|
flashIfRequested: ->
|
||||||
return unless flash = @decoration.consumeNextFlash()
|
if @props.state.flashCount > @currentFlashCount
|
||||||
|
@currentFlashCount = @props.state.flashCount
|
||||||
|
|
||||||
node = @getDOMNode()
|
node = @getDOMNode()
|
||||||
node.classList.remove(flash.class)
|
{flashClass, flashDuration} = @props.state
|
||||||
|
|
||||||
requestAnimationFrame =>
|
addFlashClass = =>
|
||||||
node.classList.add(flash.class)
|
node.classList.add(flashClass)
|
||||||
clearTimeout(@flashTimeoutId)
|
@currentFlashClass = flashClass
|
||||||
removeFlashClass = -> node.classList.remove(flash.class)
|
@flashTimeoutId = setTimeout(removeFlashClass, flashDuration)
|
||||||
@flashTimeoutId = setTimeout(removeFlashClass, flash.duration)
|
|
||||||
|
|
||||||
renderSingleLineRegions: (regionClass) ->
|
removeFlashClass = =>
|
||||||
{startPixelPosition, endPixelPosition, lineHeightInPixels} = @props
|
node.classList.remove(@currentFlashClass)
|
||||||
|
@currentFlashClass = null
|
||||||
|
clearTimeout(@flashTimeoutId)
|
||||||
|
|
||||||
className = 'region'
|
if @currentFlashClass?
|
||||||
className += " #{regionClass}" if regionClass?
|
removeFlashClass()
|
||||||
|
requestAnimationFrame(addFlashClass)
|
||||||
[
|
else
|
||||||
div className: className, key: 0, style:
|
addFlashClass()
|
||||||
top: startPixelPosition.top
|
|
||||||
height: lineHeightInPixels
|
|
||||||
left: startPixelPosition.left
|
|
||||||
width: endPixelPosition.left - startPixelPosition.left
|
|
||||||
]
|
|
||||||
|
|
||||||
renderMultiLineRegions: (regionClass) ->
|
|
||||||
{startPixelPosition, endPixelPosition, lineHeightInPixels} = @props
|
|
||||||
|
|
||||||
className = 'region'
|
|
||||||
className += " #{regionClass}" if regionClass?
|
|
||||||
|
|
||||||
regions = []
|
|
||||||
index = 0
|
|
||||||
|
|
||||||
# First row, extending from selection start to the right side of screen
|
|
||||||
regions.push(
|
|
||||||
div className: className, key: index++, style:
|
|
||||||
top: startPixelPosition.top
|
|
||||||
left: startPixelPosition.left
|
|
||||||
height: lineHeightInPixels
|
|
||||||
right: 0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Middle rows, extending from left side to right side of screen
|
|
||||||
if endPixelPosition.top - startPixelPosition.top > lineHeightInPixels
|
|
||||||
regions.push(
|
|
||||||
div className: className, key: index++, style:
|
|
||||||
top: startPixelPosition.top + lineHeightInPixels
|
|
||||||
height: endPixelPosition.top - startPixelPosition.top - lineHeightInPixels
|
|
||||||
left: 0
|
|
||||||
right: 0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Last row, extending from left side of screen to selection end
|
|
||||||
regions.push(
|
|
||||||
div className: className, key: index, style:
|
|
||||||
top: endPixelPosition.top
|
|
||||||
height: lineHeightInPixels
|
|
||||||
left: 0
|
|
||||||
width: endPixelPosition.left
|
|
||||||
)
|
|
||||||
|
|
||||||
regions
|
|
||||||
|
|
||||||
shouldComponentUpdate: (newProps) ->
|
|
||||||
not isEqualForProperties(newProps, @props, 'startPixelPosition', 'endPixelPosition', 'lineHeightInPixels', 'decoration')
|
|
||||||
|
|||||||
@@ -9,16 +9,13 @@ HighlightsComponent = React.createClass
|
|||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
div className: 'highlights',
|
div className: 'highlights',
|
||||||
@renderHighlights() if @props.performedInitialMeasurement
|
@renderHighlights()
|
||||||
|
|
||||||
renderHighlights: ->
|
renderHighlights: ->
|
||||||
{editor, highlightDecorations, lineHeightInPixels} = @props
|
{presenter} = @props
|
||||||
|
|
||||||
highlightComponents = []
|
highlightComponents = []
|
||||||
for markerId, {startPixelPosition, endPixelPosition, decorations} of highlightDecorations
|
for key, state of presenter.state.content.highlights
|
||||||
for decoration in decorations
|
highlightComponents.push(HighlightComponent({key, state}))
|
||||||
highlightComponents.push(HighlightComponent({editor, key: "#{markerId}-#{decoration.id}", startPixelPosition, endPixelPosition, decoration, lineHeightInPixels}))
|
|
||||||
|
|
||||||
highlightComponents
|
highlightComponents
|
||||||
|
|
||||||
componentDidMount: ->
|
componentDidMount: ->
|
||||||
@@ -26,6 +23,3 @@ HighlightsComponent = React.createClass
|
|||||||
insertionPoint = document.createElement('content')
|
insertionPoint = document.createElement('content')
|
||||||
insertionPoint.setAttribute('select', '.underlayer')
|
insertionPoint.setAttribute('select', '.underlayer')
|
||||||
@getDOMNode().appendChild(insertionPoint)
|
@getDOMNode().appendChild(insertionPoint)
|
||||||
|
|
||||||
shouldComponentUpdate: (newProps) ->
|
|
||||||
not isEqualForProperties(newProps, @props, 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth', 'scopedCharacterWidthsChangeCount')
|
|
||||||
|
|||||||
+104
-145
@@ -4,7 +4,6 @@ React = require 'react-atom-fork'
|
|||||||
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
||||||
{$$} = require 'space-pen'
|
{$$} = require 'space-pen'
|
||||||
|
|
||||||
Decoration = require './decoration'
|
|
||||||
CursorsComponent = require './cursors-component'
|
CursorsComponent = require './cursors-component'
|
||||||
HighlightsComponent = require './highlights-component'
|
HighlightsComponent = require './highlights-component'
|
||||||
OverlayManager = require './overlay-manager'
|
OverlayManager = require './overlay-manager'
|
||||||
@@ -18,33 +17,26 @@ LinesComponent = React.createClass
|
|||||||
displayName: 'LinesComponent'
|
displayName: 'LinesComponent'
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
{performedInitialMeasurement, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
|
{editor, presenter} = @props
|
||||||
|
@oldState ?= {lines: {}}
|
||||||
|
@newState = presenter.state.content
|
||||||
|
|
||||||
if performedInitialMeasurement
|
{scrollHeight, scrollWidth, backgroundColor, placeholderText} = @newState
|
||||||
{editor, overlayDecorations, highlightDecorations, scrollHeight, scrollWidth, placeholderText, backgroundColor} = @props
|
|
||||||
{lineHeightInPixels, defaultCharWidth, scrollViewHeight, scopedCharacterWidthsChangeCount} = @props
|
style =
|
||||||
{scrollTop, scrollLeft, cursorPixelRects} = @props
|
height: scrollHeight
|
||||||
style =
|
width: scrollWidth
|
||||||
height: Math.max(scrollHeight, scrollViewHeight)
|
WebkitTransform: @getTransform()
|
||||||
width: scrollWidth
|
backgroundColor: backgroundColor
|
||||||
WebkitTransform: @getTransform()
|
|
||||||
backgroundColor: if editor.isMini() then null else backgroundColor
|
|
||||||
|
|
||||||
div {className: 'lines', style},
|
div {className: 'lines', style},
|
||||||
div className: 'placeholder-text', placeholderText if placeholderText?
|
div className: 'placeholder-text', placeholderText if placeholderText?
|
||||||
|
CursorsComponent {presenter}
|
||||||
CursorsComponent {
|
HighlightsComponent {presenter}
|
||||||
cursorPixelRects, cursorBlinkPeriod, cursorBlinkResumeDelay, lineHeightInPixels,
|
|
||||||
defaultCharWidth, scopedCharacterWidthsChangeCount, performedInitialMeasurement
|
|
||||||
}
|
|
||||||
|
|
||||||
HighlightsComponent {
|
|
||||||
editor, highlightDecorations, lineHeightInPixels, defaultCharWidth,
|
|
||||||
scopedCharacterWidthsChangeCount, performedInitialMeasurement
|
|
||||||
}
|
|
||||||
|
|
||||||
getTransform: ->
|
getTransform: ->
|
||||||
{scrollTop, scrollLeft, useHardwareAcceleration} = @props
|
{scrollTop, scrollLeft} = @newState
|
||||||
|
{useHardwareAcceleration} = @props
|
||||||
|
|
||||||
if useHardwareAcceleration
|
if useHardwareAcceleration
|
||||||
"translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
|
"translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
|
||||||
@@ -52,7 +44,7 @@ LinesComponent = React.createClass
|
|||||||
"translate(#{-scrollLeft}px, #{-scrollTop}px)"
|
"translate(#{-scrollLeft}px, #{-scrollTop}px)"
|
||||||
|
|
||||||
componentWillMount: ->
|
componentWillMount: ->
|
||||||
@measuredLines = new WeakSet
|
@measuredLines = new Set
|
||||||
@lineNodesByLineId = {}
|
@lineNodesByLineId = {}
|
||||||
@screenRowsByLineId = {}
|
@screenRowsByLineId = {}
|
||||||
@lineIdsByScreenRow = {}
|
@lineIdsByScreenRow = {}
|
||||||
@@ -71,124 +63,91 @@ LinesComponent = React.createClass
|
|||||||
else
|
else
|
||||||
@overlayManager = new OverlayManager(@getDOMNode())
|
@overlayManager = new OverlayManager(@getDOMNode())
|
||||||
|
|
||||||
shouldComponentUpdate: (newProps) ->
|
componentDidUpdate: ->
|
||||||
return true unless isEqualForProperties(newProps, @props,
|
{visible, presenter} = @props
|
||||||
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
|
|
||||||
'overlayDecorations', 'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'visible',
|
|
||||||
'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount', 'lineWidth', 'useHardwareAcceleration',
|
|
||||||
'placeholderText', 'performedInitialMeasurement', 'backgroundColor', 'cursorPixelRects'
|
|
||||||
)
|
|
||||||
|
|
||||||
{renderedRowRange, pendingChanges} = newProps
|
@removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
|
||||||
return false unless renderedRowRange?
|
@updateLineNodes()
|
||||||
|
@measureCharactersInNewLines() if visible and not @newState.scrollingVertically
|
||||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
|
||||||
for change in pendingChanges
|
|
||||||
if change.screenDelta is 0
|
|
||||||
return true unless change.end < renderedStartRow or renderedEndRow <= change.start
|
|
||||||
else
|
|
||||||
return true unless renderedEndRow <= change.start
|
|
||||||
|
|
||||||
false
|
|
||||||
|
|
||||||
componentDidUpdate: (prevProps) ->
|
|
||||||
{visible, scrollingVertically, performedInitialMeasurement} = @props
|
|
||||||
return unless performedInitialMeasurement
|
|
||||||
|
|
||||||
@clearScreenRowCaches() unless prevProps.lineHeightInPixels is @props.lineHeightInPixels
|
|
||||||
@removeLineNodes() unless isEqualForProperties(prevProps, @props, 'showIndentGuide')
|
|
||||||
@updateLines(@props.lineWidth isnt prevProps.lineWidth)
|
|
||||||
@measureCharactersInNewLines() if visible and not scrollingVertically
|
|
||||||
|
|
||||||
@overlayManager?.render(@props)
|
@overlayManager?.render(@props)
|
||||||
|
|
||||||
|
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
|
||||||
|
@oldState.scrollWidth = @newState.scrollWidth
|
||||||
|
|
||||||
clearScreenRowCaches: ->
|
clearScreenRowCaches: ->
|
||||||
@screenRowsByLineId = {}
|
@screenRowsByLineId = {}
|
||||||
@lineIdsByScreenRow = {}
|
@lineIdsByScreenRow = {}
|
||||||
|
|
||||||
updateLines: (updateWidth) ->
|
removeLineNodes: ->
|
||||||
{tokenizedLines, renderedRowRange, showIndentGuide, selectionChanged, lineDecorations} = @props
|
@removeLineNode(id) for id of @oldState.lines
|
||||||
[startRow] = renderedRowRange
|
|
||||||
|
|
||||||
@removeLineNodes(tokenizedLines)
|
removeLineNode: (id) ->
|
||||||
@appendOrUpdateVisibleLineNodes(tokenizedLines, startRow, updateWidth)
|
@lineNodesByLineId[id].remove()
|
||||||
|
delete @lineNodesByLineId[id]
|
||||||
|
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
|
||||||
|
delete @screenRowsByLineId[id]
|
||||||
|
delete @oldState.lines[id]
|
||||||
|
|
||||||
removeLineNodes: (visibleLines=[]) ->
|
updateLineNodes: ->
|
||||||
{mouseWheelScreenRow} = @props
|
{presenter} = @props
|
||||||
visibleLineIds = new Set
|
|
||||||
visibleLineIds.add(line.id.toString()) for line in visibleLines
|
|
||||||
node = @getDOMNode()
|
|
||||||
for lineId, lineNode of @lineNodesByLineId when not visibleLineIds.has(lineId)
|
|
||||||
screenRow = @screenRowsByLineId[lineId]
|
|
||||||
if not screenRow? or screenRow isnt mouseWheelScreenRow
|
|
||||||
delete @lineNodesByLineId[lineId]
|
|
||||||
delete @lineIdsByScreenRow[screenRow] if @lineIdsByScreenRow[screenRow] is lineId
|
|
||||||
delete @screenRowsByLineId[lineId]
|
|
||||||
delete @renderedDecorationsByLineId[lineId]
|
|
||||||
node.removeChild(lineNode)
|
|
||||||
|
|
||||||
appendOrUpdateVisibleLineNodes: (visibleLines, startRow, updateWidth) ->
|
for id of @oldState.lines
|
||||||
{lineDecorations} = @props
|
unless @newState.lines.hasOwnProperty(id)
|
||||||
|
@removeLineNode(id)
|
||||||
|
|
||||||
newLines = null
|
newLineIds = null
|
||||||
newLinesHTML = null
|
newLinesHTML = null
|
||||||
|
|
||||||
for line, index in visibleLines
|
for id, lineState of @newState.lines
|
||||||
screenRow = startRow + index
|
if @oldState.lines.hasOwnProperty(id)
|
||||||
|
@updateLineNode(id)
|
||||||
if @hasLineNode(line.id)
|
|
||||||
@updateLineNode(line, screenRow, updateWidth)
|
|
||||||
else
|
else
|
||||||
newLines ?= []
|
newLineIds ?= []
|
||||||
newLinesHTML ?= ""
|
newLinesHTML ?= ""
|
||||||
newLines.push(line)
|
newLineIds.push(id)
|
||||||
newLinesHTML += @buildLineHTML(line, screenRow)
|
newLinesHTML += @buildLineHTML(id)
|
||||||
@screenRowsByLineId[line.id] = screenRow
|
@screenRowsByLineId[id] = lineState.screenRow
|
||||||
@lineIdsByScreenRow[screenRow] = line.id
|
@lineIdsByScreenRow[lineState.screenRow] = id
|
||||||
|
@oldState.lines[id] = _.clone(lineState)
|
||||||
|
|
||||||
@renderedDecorationsByLineId[line.id] = lineDecorations[screenRow]
|
return unless newLineIds?
|
||||||
|
|
||||||
return unless newLines?
|
|
||||||
|
|
||||||
WrapperDiv.innerHTML = newLinesHTML
|
WrapperDiv.innerHTML = newLinesHTML
|
||||||
newLineNodes = toArray(WrapperDiv.children)
|
newLineNodes = toArray(WrapperDiv.children)
|
||||||
node = @getDOMNode()
|
node = @getDOMNode()
|
||||||
for line, i in newLines
|
for id, i in newLineIds
|
||||||
lineNode = newLineNodes[i]
|
lineNode = newLineNodes[i]
|
||||||
@lineNodesByLineId[line.id] = lineNode
|
@lineNodesByLineId[id] = lineNode
|
||||||
node.appendChild(lineNode)
|
node.appendChild(lineNode)
|
||||||
|
|
||||||
hasLineNode: (lineId) ->
|
buildLineHTML: (id) ->
|
||||||
@lineNodesByLineId.hasOwnProperty(lineId)
|
{presenter} = @props
|
||||||
|
{scrollWidth} = @newState
|
||||||
buildLineHTML: (line, screenRow) ->
|
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id]
|
||||||
{showIndentGuide, lineHeightInPixels, lineDecorations, lineWidth} = @props
|
|
||||||
{tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line
|
|
||||||
|
|
||||||
classes = ''
|
classes = ''
|
||||||
if decorations = lineDecorations[screenRow]
|
if decorationClasses?
|
||||||
for id, decoration of decorations
|
for decorationClass in decorationClasses
|
||||||
if Decoration.isType(decoration, 'line')
|
classes += decorationClass + ' '
|
||||||
classes += decoration.class + ' '
|
|
||||||
classes += 'line'
|
classes += 'line'
|
||||||
|
|
||||||
top = screenRow * lineHeightInPixels
|
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{scrollWidth}px;\" data-screen-row=\"#{screenRow}\">"
|
||||||
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{lineWidth}px;\" data-screen-row=\"#{screenRow}\">"
|
|
||||||
|
|
||||||
if text is ""
|
if text is ""
|
||||||
lineHTML += @buildEmptyLineInnerHTML(line)
|
lineHTML += @buildEmptyLineInnerHTML(id)
|
||||||
else
|
else
|
||||||
lineHTML += @buildLineInnerHTML(line)
|
lineHTML += @buildLineInnerHTML(id)
|
||||||
|
|
||||||
lineHTML += '<span class="fold-marker"></span>' if fold
|
lineHTML += '<span class="fold-marker"></span>' if fold
|
||||||
lineHTML += "</div>"
|
lineHTML += "</div>"
|
||||||
lineHTML
|
lineHTML
|
||||||
|
|
||||||
buildEmptyLineInnerHTML: (line) ->
|
buildEmptyLineInnerHTML: (id) ->
|
||||||
{showIndentGuide} = @props
|
{indentGuidesVisible} = @newState
|
||||||
{indentLevel, tabLength, endOfLineInvisibles} = line
|
{indentLevel, tabLength, endOfLineInvisibles} = @newState.lines[id]
|
||||||
|
|
||||||
if showIndentGuide and indentLevel > 0
|
if indentGuidesVisible and indentLevel > 0
|
||||||
invisibleIndex = 0
|
invisibleIndex = 0
|
||||||
lineHTML = ''
|
lineHTML = ''
|
||||||
for i in [0...indentLevel]
|
for i in [0...indentLevel]
|
||||||
@@ -201,30 +160,30 @@ LinesComponent = React.createClass
|
|||||||
lineHTML += "</span>"
|
lineHTML += "</span>"
|
||||||
|
|
||||||
while invisibleIndex < endOfLineInvisibles?.length
|
while invisibleIndex < endOfLineInvisibles?.length
|
||||||
lineHTML += "<span class='invisible-character'>#{line.endOfLineInvisibles[invisibleIndex++]}</span>"
|
lineHTML += "<span class='invisible-character'>#{endOfLineInvisibles[invisibleIndex++]}</span>"
|
||||||
|
|
||||||
lineHTML
|
lineHTML
|
||||||
else
|
else
|
||||||
@buildEndOfLineHTML(line) or ' '
|
@buildEndOfLineHTML(id) or ' '
|
||||||
|
|
||||||
buildLineInnerHTML: (line) ->
|
buildLineInnerHTML: (id) ->
|
||||||
{editor, showIndentGuide} = @props
|
{editor} = @props
|
||||||
{tokens, text} = line
|
{indentGuidesVisible} = @newState
|
||||||
|
{tokens, text, isOnlyWhitespace} = @newState.lines[id]
|
||||||
innerHTML = ""
|
innerHTML = ""
|
||||||
|
|
||||||
scopeStack = []
|
scopeStack = []
|
||||||
lineIsWhitespaceOnly = line.isOnlyWhitespace()
|
|
||||||
for token in tokens
|
for token in tokens
|
||||||
innerHTML += @updateScopeStack(scopeStack, token.scopes)
|
innerHTML += @updateScopeStack(scopeStack, token.scopes)
|
||||||
hasIndentGuide = not editor.isMini() and showIndentGuide and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and lineIsWhitespaceOnly))
|
hasIndentGuide = indentGuidesVisible and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and isOnlyWhitespace))
|
||||||
innerHTML += token.getValueAsHtml({hasIndentGuide})
|
innerHTML += token.getValueAsHtml({hasIndentGuide})
|
||||||
|
|
||||||
innerHTML += @popScope(scopeStack) while scopeStack.length > 0
|
innerHTML += @popScope(scopeStack) while scopeStack.length > 0
|
||||||
innerHTML += @buildEndOfLineHTML(line)
|
innerHTML += @buildEndOfLineHTML(id)
|
||||||
innerHTML
|
innerHTML
|
||||||
|
|
||||||
buildEndOfLineHTML: (line) ->
|
buildEndOfLineHTML: (id) ->
|
||||||
{endOfLineInvisibles} = line
|
{endOfLineInvisibles} = @newState.lines[id]
|
||||||
|
|
||||||
html = ''
|
html = ''
|
||||||
if endOfLineInvisibles?
|
if endOfLineInvisibles?
|
||||||
@@ -257,33 +216,30 @@ LinesComponent = React.createClass
|
|||||||
scopeStack.push(scope)
|
scopeStack.push(scope)
|
||||||
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
|
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
|
||||||
|
|
||||||
updateLineNode: (line, screenRow, updateWidth) ->
|
updateLineNode: (id) ->
|
||||||
{lineHeightInPixels, lineDecorations, lineWidth} = @props
|
{scrollWidth} = @newState
|
||||||
lineNode = @lineNodesByLineId[line.id]
|
{screenRow, top} = @newState.lines[id]
|
||||||
|
|
||||||
decorations = lineDecorations[screenRow]
|
lineNode = @lineNodesByLineId[id]
|
||||||
previousDecorations = @renderedDecorationsByLineId[line.id]
|
|
||||||
|
|
||||||
if previousDecorations?
|
newDecorationClasses = @newState.lines[id].decorationClasses
|
||||||
for id, decoration of previousDecorations
|
oldDecorationClasses = @oldState.lines[id].decorationClasses
|
||||||
if Decoration.isType(decoration, 'line') and not @hasDecoration(decorations, decoration)
|
|
||||||
lineNode.classList.remove(decoration.class)
|
|
||||||
|
|
||||||
if decorations?
|
if oldDecorationClasses?
|
||||||
for id, decoration of decorations
|
for decorationClass in oldDecorationClasses
|
||||||
if Decoration.isType(decoration, 'line') and not @hasDecoration(previousDecorations, decoration)
|
unless newDecorationClasses? and decorationClass in newDecorationClasses
|
||||||
lineNode.classList.add(decoration.class)
|
lineNode.classList.remove(decorationClass)
|
||||||
|
|
||||||
lineNode.style.width = lineWidth + 'px' if updateWidth
|
if newDecorationClasses?
|
||||||
|
for decorationClass in newDecorationClasses
|
||||||
|
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
|
||||||
|
lineNode.classList.add(decorationClass)
|
||||||
|
|
||||||
unless @screenRowsByLineId[line.id] is screenRow
|
lineNode.style.width = scrollWidth + 'px'
|
||||||
lineNode.style.top = screenRow * lineHeightInPixels + 'px'
|
lineNode.style.top = top + 'px'
|
||||||
lineNode.dataset.screenRow = screenRow
|
lineNode.dataset.screenRow = screenRow
|
||||||
@screenRowsByLineId[line.id] = screenRow
|
@screenRowsByLineId[id] = screenRow
|
||||||
@lineIdsByScreenRow[screenRow] = line.id
|
@lineIdsByScreenRow[screenRow] = id
|
||||||
|
|
||||||
hasDecoration: (decorations, decoration) ->
|
|
||||||
decorations? and decorations[decoration.id] is decoration
|
|
||||||
|
|
||||||
lineNodeForScreenRow: (screenRow) ->
|
lineNodeForScreenRow: (screenRow) ->
|
||||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||||
@@ -295,26 +251,27 @@ LinesComponent = React.createClass
|
|||||||
charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
|
charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
|
||||||
node.removeChild(DummyLineNode)
|
node.removeChild(DummyLineNode)
|
||||||
|
|
||||||
{editor} = @props
|
{editor, presenter} = @props
|
||||||
|
presenter.setLineHeight(lineHeightInPixels)
|
||||||
editor.setLineHeightInPixels(lineHeightInPixels)
|
editor.setLineHeightInPixels(lineHeightInPixels)
|
||||||
|
presenter.setBaseCharacterWidth(charWidth)
|
||||||
editor.setDefaultCharWidth(charWidth)
|
editor.setDefaultCharWidth(charWidth)
|
||||||
|
|
||||||
remeasureCharacterWidths: ->
|
remeasureCharacterWidths: ->
|
||||||
return unless @props.performedInitialMeasurement
|
return unless @props.presenter.hasRequiredMeasurements()
|
||||||
|
|
||||||
@clearScopedCharWidths()
|
@clearScopedCharWidths()
|
||||||
@measureCharactersInNewLines()
|
@measureCharactersInNewLines()
|
||||||
|
|
||||||
measureCharactersInNewLines: ->
|
measureCharactersInNewLines: ->
|
||||||
{editor, tokenizedLines, renderedRowRange} = @props
|
{editor} = @props
|
||||||
[visibleStartRow] = renderedRowRange
|
|
||||||
node = @getDOMNode()
|
node = @getDOMNode()
|
||||||
|
|
||||||
editor.batchCharacterMeasurement =>
|
editor.batchCharacterMeasurement =>
|
||||||
for tokenizedLine in tokenizedLines
|
for id, lineState of @oldState.lines
|
||||||
unless @measuredLines.has(tokenizedLine)
|
unless @measuredLines.has(id)
|
||||||
lineNode = @lineNodesByLineId[tokenizedLine.id]
|
lineNode = @lineNodesByLineId[id]
|
||||||
@measureCharactersInLine(tokenizedLine, lineNode)
|
@measureCharactersInLine(lineState, lineNode)
|
||||||
return
|
return
|
||||||
|
|
||||||
measureCharactersInLine: (tokenizedLine, lineNode) ->
|
measureCharactersInLine: (tokenizedLine, lineNode) ->
|
||||||
@@ -357,11 +314,13 @@ LinesComponent = React.createClass
|
|||||||
rangeForMeasurement.setEnd(textNode, i + charLength)
|
rangeForMeasurement.setEnd(textNode, i + charLength)
|
||||||
charWidth = rangeForMeasurement.getBoundingClientRect().width
|
charWidth = rangeForMeasurement.getBoundingClientRect().width
|
||||||
editor.setScopedCharWidth(scopes, char, charWidth)
|
editor.setScopedCharWidth(scopes, char, charWidth)
|
||||||
|
@props.presenter.setScopedCharWidth(scopes, char, charWidth)
|
||||||
|
|
||||||
charIndex += charLength
|
charIndex += charLength
|
||||||
|
|
||||||
@measuredLines.add(tokenizedLine)
|
@measuredLines.add(tokenizedLine.id)
|
||||||
|
|
||||||
clearScopedCharWidths: ->
|
clearScopedCharWidths: ->
|
||||||
@measuredLines.clear()
|
@measuredLines.clear()
|
||||||
@props.editor.clearScopedCharWidths()
|
@props.editor.clearScopedCharWidths()
|
||||||
|
@props.presenter.clearScopedCharWidths()
|
||||||
|
|||||||
@@ -1,46 +1,41 @@
|
|||||||
module.exports =
|
module.exports =
|
||||||
class OverlayManager
|
class OverlayManager
|
||||||
constructor: (@container) ->
|
constructor: (@container) ->
|
||||||
@overlays = {}
|
@overlayNodesById = {}
|
||||||
|
|
||||||
render: (props) ->
|
render: (props) ->
|
||||||
{editor, overlayDecorations, lineHeightInPixels} = props
|
{presenter} = props
|
||||||
|
|
||||||
existingDecorations = null
|
for decorationId, {pixelPosition, item} of presenter.state.content.overlays
|
||||||
for markerId, {headPixelPosition, tailPixelPosition, decorations} of overlayDecorations
|
@renderOverlay(presenter, decorationId, item, pixelPosition)
|
||||||
for decoration in decorations
|
|
||||||
pixelPosition =
|
|
||||||
if decoration.position is 'tail' then tailPixelPosition else headPixelPosition
|
|
||||||
|
|
||||||
@renderOverlay(editor, decoration, pixelPosition, lineHeightInPixels)
|
for id, overlayNode of @overlayNodesById
|
||||||
|
unless presenter.state.content.overlays.hasOwnProperty(id)
|
||||||
existingDecorations ?= {}
|
overlayNode.remove()
|
||||||
existingDecorations[decoration.id] = true
|
delete @overlayNodesById[id]
|
||||||
|
|
||||||
for id, overlay of @overlays
|
|
||||||
unless existingDecorations? and id of existingDecorations
|
|
||||||
@container.removeChild(overlay)
|
|
||||||
delete @overlays[id]
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
renderOverlay: (editor, decoration, pixelPosition, lineHeightInPixels) ->
|
renderOverlay: (presenter, decorationId, item, pixelPosition) ->
|
||||||
item = atom.views.getView(decoration.item)
|
item = atom.views.getView(item)
|
||||||
unless overlay = @overlays[decoration.id]
|
unless overlayNode = @overlayNodesById[decorationId]
|
||||||
overlay = @overlays[decoration.id] = document.createElement('atom-overlay')
|
overlayNode = @overlayNodesById[decorationId] = document.createElement('atom-overlay')
|
||||||
overlay.appendChild(item)
|
overlayNode.appendChild(item)
|
||||||
@container.appendChild(overlay)
|
@container.appendChild(overlayNode)
|
||||||
|
|
||||||
itemWidth = item.offsetWidth
|
itemWidth = item.offsetWidth
|
||||||
itemHeight = item.offsetHeight
|
itemHeight = item.offsetHeight
|
||||||
|
|
||||||
|
|
||||||
|
{scrollTop, scrollLeft} = presenter.state.content
|
||||||
|
|
||||||
left = pixelPosition.left
|
left = pixelPosition.left
|
||||||
if left + itemWidth - editor.getScrollLeft() > editor.getWidth() and left - itemWidth >= editor.getScrollLeft()
|
if left + itemWidth - scrollLeft > presenter.contentFrameWidth and left - itemWidth >= scrollLeft
|
||||||
left -= itemWidth
|
left -= itemWidth
|
||||||
|
|
||||||
top = pixelPosition.top + lineHeightInPixels
|
top = pixelPosition.top + presenter.lineHeight
|
||||||
if top + itemHeight - editor.getScrollTop() > editor.getHeight() and top - itemHeight - lineHeightInPixels >= editor.getScrollTop()
|
if top + itemHeight - scrollTop > presenter.computeHeight() and top - itemHeight - presenter.lineHeight >= scrollTop
|
||||||
top -= itemHeight + lineHeightInPixels
|
top -= itemHeight + presenter.lineHeight
|
||||||
|
|
||||||
overlay.style.top = top + 'px'
|
overlayNode.style.top = top + 'px'
|
||||||
overlay.style.left = left + 'px'
|
overlayNode.style.left = left + 'px'
|
||||||
|
|||||||
@@ -7,28 +7,33 @@ ScrollbarComponent = React.createClass
|
|||||||
displayName: 'ScrollbarComponent'
|
displayName: 'ScrollbarComponent'
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
{orientation, className, scrollHeight, scrollWidth, visible} = @props
|
{presenter, orientation, className, useHardwareAcceleration} = @props
|
||||||
{scrollableInOppositeDirection, horizontalScrollbarHeight, verticalScrollbarWidth} = @props
|
|
||||||
{useHardwareAcceleration} = @props
|
switch orientation
|
||||||
|
when 'vertical'
|
||||||
|
@newState = presenter.state.verticalScrollbar
|
||||||
|
when 'horizontal'
|
||||||
|
@newState = presenter.state.horizontalScrollbar
|
||||||
|
|
||||||
style = {}
|
style = {}
|
||||||
style.display = 'none' unless visible
|
|
||||||
|
style.display = 'none' unless @newState.visible
|
||||||
style.transform = 'translateZ(0)' if useHardwareAcceleration # See atom/atom#3559
|
style.transform = 'translateZ(0)' if useHardwareAcceleration # See atom/atom#3559
|
||||||
switch orientation
|
switch orientation
|
||||||
when 'vertical'
|
when 'vertical'
|
||||||
style.width = verticalScrollbarWidth
|
style.width = @newState.width
|
||||||
style.bottom = horizontalScrollbarHeight if scrollableInOppositeDirection
|
style.bottom = @newState.bottom
|
||||||
when 'horizontal'
|
when 'horizontal'
|
||||||
style.left = 0
|
style.left = 0
|
||||||
style.right = verticalScrollbarWidth if scrollableInOppositeDirection
|
style.right = @newState.right
|
||||||
style.height = horizontalScrollbarHeight
|
style.height = @newState.height
|
||||||
|
|
||||||
div {className, style},
|
div {className, style},
|
||||||
switch orientation
|
switch orientation
|
||||||
when 'vertical'
|
when 'vertical'
|
||||||
div className: 'scrollbar-content', style: {height: scrollHeight}
|
div className: 'scrollbar-content', style: {height: @newState.scrollHeight}
|
||||||
when 'horizontal'
|
when 'horizontal'
|
||||||
div className: 'scrollbar-content', style: {width: scrollWidth}
|
div className: 'scrollbar-content', style: {width: @newState.scrollWidth}
|
||||||
|
|
||||||
componentDidMount: ->
|
componentDidMount: ->
|
||||||
{orientation} = @props
|
{orientation} = @props
|
||||||
@@ -41,26 +46,15 @@ ScrollbarComponent = React.createClass
|
|||||||
componentWillUnmount: ->
|
componentWillUnmount: ->
|
||||||
@getDOMNode().removeEventListener 'scroll', @onScroll
|
@getDOMNode().removeEventListener 'scroll', @onScroll
|
||||||
|
|
||||||
shouldComponentUpdate: (newProps) ->
|
|
||||||
return true if newProps.visible isnt @props.visible
|
|
||||||
|
|
||||||
switch @props.orientation
|
|
||||||
when 'vertical'
|
|
||||||
not isEqualForProperties(newProps, @props, 'scrollHeight', 'scrollTop', 'scrollableInOppositeDirection', 'verticalScrollbarWidth')
|
|
||||||
when 'horizontal'
|
|
||||||
not isEqualForProperties(newProps, @props, 'scrollWidth', 'scrollLeft', 'scrollableInOppositeDirection', 'horizontalScrollbarHeight')
|
|
||||||
|
|
||||||
componentDidUpdate: ->
|
componentDidUpdate: ->
|
||||||
{orientation, scrollTop, scrollLeft} = @props
|
{orientation} = @props
|
||||||
node = @getDOMNode()
|
node = @getDOMNode()
|
||||||
|
|
||||||
switch orientation
|
switch orientation
|
||||||
when 'vertical'
|
when 'vertical'
|
||||||
node.scrollTop = scrollTop
|
node.scrollTop = @newState.scrollTop
|
||||||
@props.scrollTop = node.scrollTop # Ensure scrollTop reflects actual DOM without triggering another update
|
|
||||||
when 'horizontal'
|
when 'horizontal'
|
||||||
node.scrollLeft = scrollLeft
|
node.scrollLeft = @newState.scrollLeft
|
||||||
@props.scrollLeft = node.scrollLeft # Ensure scrollLeft reflects actual DOM without triggering another update
|
|
||||||
|
|
||||||
onScroll: ->
|
onScroll: ->
|
||||||
{orientation, onScroll} = @props
|
{orientation, onScroll} = @props
|
||||||
@@ -69,9 +63,7 @@ ScrollbarComponent = React.createClass
|
|||||||
switch orientation
|
switch orientation
|
||||||
when 'vertical'
|
when 'vertical'
|
||||||
scrollTop = node.scrollTop
|
scrollTop = node.scrollTop
|
||||||
@props.scrollTop = scrollTop # Ensure scrollTop reflects actual DOM without triggering another update
|
|
||||||
onScroll(scrollTop)
|
onScroll(scrollTop)
|
||||||
when 'horizontal'
|
when 'horizontal'
|
||||||
scrollLeft = node.scrollLeft
|
scrollLeft = node.scrollLeft
|
||||||
@props.scrollLeft = scrollLeft # Ensure scrollLeft reflects actual DOM without triggering another update
|
|
||||||
onScroll(scrollLeft)
|
onScroll(scrollLeft)
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ ScrollbarCornerComponent = React.createClass
|
|||||||
displayName: 'ScrollbarCornerComponent'
|
displayName: 'ScrollbarCornerComponent'
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
{visible, measuringScrollbars, width, height} = @props
|
{presenter, measuringScrollbars} = @props
|
||||||
|
|
||||||
|
visible = presenter.state.horizontalScrollbar.visible and presenter.state.verticalScrollbar.visible
|
||||||
|
width = presenter.state.verticalScrollbar.width
|
||||||
|
height = presenter.state.horizontalScrollbar.height
|
||||||
|
|
||||||
if measuringScrollbars
|
if measuringScrollbars
|
||||||
height = 25
|
height = 25
|
||||||
@@ -19,6 +23,3 @@ ScrollbarCornerComponent = React.createClass
|
|||||||
div style:
|
div style:
|
||||||
height: height + 1
|
height: height + 1
|
||||||
width: width + 1
|
width: width + 1
|
||||||
|
|
||||||
shouldComponentUpdate: (newProps) ->
|
|
||||||
not isEqualForProperties(newProps, @props, 'measuringScrollbars', 'visible', 'width', 'height')
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ grim = require 'grim'
|
|||||||
{CompositeDisposable} = require 'event-kit'
|
{CompositeDisposable} = require 'event-kit'
|
||||||
ipc = require 'ipc'
|
ipc = require 'ipc'
|
||||||
|
|
||||||
|
TextEditorPresenter = require './text-editor-presenter'
|
||||||
GutterComponent = require './gutter-component'
|
GutterComponent = require './gutter-component'
|
||||||
InputComponent = require './input-component'
|
InputComponent = require './input-component'
|
||||||
LinesComponent = require './lines-component'
|
LinesComponent = require './lines-component'
|
||||||
@@ -21,9 +22,6 @@ TextEditorComponent = React.createClass
|
|||||||
mixins: [SubscriberMixin]
|
mixins: [SubscriberMixin]
|
||||||
|
|
||||||
visible: false
|
visible: false
|
||||||
autoHeight: false
|
|
||||||
backgroundColor: null
|
|
||||||
gutterBackgroundColor: null
|
|
||||||
pendingScrollTop: null
|
pendingScrollTop: null
|
||||||
pendingScrollLeft: null
|
pendingScrollLeft: null
|
||||||
selectOnMouseMove: false
|
selectOnMouseMove: false
|
||||||
@@ -32,13 +30,9 @@ TextEditorComponent = React.createClass
|
|||||||
updateRequestedWhilePaused: false
|
updateRequestedWhilePaused: false
|
||||||
cursorMoved: false
|
cursorMoved: false
|
||||||
selectionChanged: false
|
selectionChanged: false
|
||||||
scrollingVertically: false
|
|
||||||
mouseWheelScreenRow: null
|
|
||||||
mouseWheelScreenRowClearDelay: 150
|
|
||||||
scrollSensitivity: 0.4
|
scrollSensitivity: 0.4
|
||||||
heightAndWidthMeasurementRequested: false
|
heightAndWidthMeasurementRequested: false
|
||||||
inputEnabled: true
|
inputEnabled: true
|
||||||
scopedCharacterWidthsChangeCount: null
|
|
||||||
domPollingInterval: 100
|
domPollingInterval: 100
|
||||||
domPollingIntervalId: null
|
domPollingIntervalId: null
|
||||||
domPollingPaused: false
|
domPollingPaused: false
|
||||||
@@ -47,46 +41,19 @@ TextEditorComponent = React.createClass
|
|||||||
remeasureCharacterWidthsWhenShown: false
|
remeasureCharacterWidthsWhenShown: false
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
{focused, showIndentGuide, showLineNumbers, visible} = @state
|
{focused, showLineNumbers} = @state
|
||||||
{editor, cursorBlinkPeriod, cursorBlinkResumeDelay, hostElement, useShadowDOM} = @props
|
{editor, cursorBlinkPeriod, cursorBlinkResumeDelay, hostElement, useShadowDOM} = @props
|
||||||
maxLineNumberDigits = editor.getLineCount().toString().length
|
|
||||||
hasSelection = editor.getLastSelection()? and !editor.getLastSelection().isEmpty()
|
hasSelection = editor.getLastSelection()? and !editor.getLastSelection().isEmpty()
|
||||||
style = {}
|
style = {}
|
||||||
|
|
||||||
@performedInitialMeasurement = false if editor.isDestroyed()
|
@performedInitialMeasurement = false if editor.isDestroyed()
|
||||||
|
|
||||||
if @performedInitialMeasurement
|
if @performedInitialMeasurement
|
||||||
renderedRowRange = @getRenderedRowRange()
|
|
||||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
|
||||||
cursorPixelRects = @getCursorPixelRects(renderedRowRange)
|
|
||||||
|
|
||||||
tokenizedLines = editor.tokenizedLinesForScreenRows(renderedStartRow, renderedEndRow - 1)
|
|
||||||
|
|
||||||
decorations = editor.decorationsForScreenRowRange(renderedStartRow, renderedEndRow)
|
|
||||||
highlightDecorations = @getHighlightDecorations(decorations)
|
|
||||||
overlayDecorations = @getOverlayDecorations(decorations)
|
|
||||||
lineDecorations = @getLineDecorations(decorations)
|
|
||||||
placeholderText = editor.getPlaceholderText() if editor.isEmpty()
|
|
||||||
visible = @isVisible()
|
visible = @isVisible()
|
||||||
|
|
||||||
scrollHeight = editor.getScrollHeight()
|
|
||||||
scrollWidth = editor.getScrollWidth()
|
|
||||||
scrollTop = editor.getScrollTop()
|
|
||||||
scrollLeft = editor.getScrollLeft()
|
|
||||||
lineHeightInPixels = editor.getLineHeightInPixels()
|
|
||||||
defaultCharWidth = editor.getDefaultCharWidth()
|
|
||||||
scrollViewHeight = editor.getHeight()
|
|
||||||
lineWidth = Math.max(scrollWidth, editor.getWidth())
|
|
||||||
horizontalScrollbarHeight = editor.getHorizontalScrollbarHeight()
|
|
||||||
verticalScrollbarWidth = editor.getVerticalScrollbarWidth()
|
|
||||||
verticallyScrollable = editor.verticallyScrollable()
|
|
||||||
horizontallyScrollable = editor.horizontallyScrollable()
|
|
||||||
hiddenInputStyle = @getHiddenInputPosition()
|
hiddenInputStyle = @getHiddenInputPosition()
|
||||||
hiddenInputStyle.WebkitTransform = 'translateZ(0)' if @useHardwareAcceleration
|
hiddenInputStyle.WebkitTransform = 'translateZ(0)' if @useHardwareAcceleration
|
||||||
if @mouseWheelScreenRow? and not (renderedStartRow <= @mouseWheelScreenRow < renderedEndRow)
|
style.height = @presenter.state.height if @presenter.state.height?
|
||||||
mouseWheelScreenRow = @mouseWheelScreenRow
|
|
||||||
|
|
||||||
style.height = scrollViewHeight if @autoHeight
|
|
||||||
|
|
||||||
if useShadowDOM
|
if useShadowDOM
|
||||||
className = 'editor-contents--private'
|
className = 'editor-contents--private'
|
||||||
@@ -98,10 +65,8 @@ TextEditorComponent = React.createClass
|
|||||||
div {className, style},
|
div {className, style},
|
||||||
if @gutterVisible
|
if @gutterVisible
|
||||||
GutterComponent {
|
GutterComponent {
|
||||||
ref: 'gutter', onMouseDown: @onGutterMouseDown, lineDecorations,
|
ref: 'gutter', onMouseDown: @onGutterMouseDown,
|
||||||
defaultCharWidth, editor, renderedRowRange, maxLineNumberDigits, scrollViewHeight,
|
@presenter, editor, @useHardwareAcceleration
|
||||||
scrollTop, scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow,
|
|
||||||
@useHardwareAcceleration, @performedInitialMeasurement, @backgroundColor, @gutterBackgroundColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div ref: 'scrollView', className: 'scroll-view',
|
div ref: 'scrollView', className: 'scroll-view',
|
||||||
@@ -111,53 +76,30 @@ TextEditorComponent = React.createClass
|
|||||||
style: hiddenInputStyle
|
style: hiddenInputStyle
|
||||||
|
|
||||||
LinesComponent {
|
LinesComponent {
|
||||||
ref: 'lines',
|
ref: 'lines', @presenter, editor, hostElement, @useHardwareAcceleration, useShadowDOM, visible
|
||||||
editor, lineHeightInPixels, defaultCharWidth, tokenizedLines,
|
|
||||||
lineDecorations, highlightDecorations, overlayDecorations, hostElement,
|
|
||||||
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft,
|
|
||||||
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow,
|
|
||||||
visible, scrollViewHeight, @scopedCharacterWidthsChangeCount, lineWidth, @useHardwareAcceleration,
|
|
||||||
placeholderText, @performedInitialMeasurement, @backgroundColor, cursorPixelRects,
|
|
||||||
cursorBlinkPeriod, cursorBlinkResumeDelay, useShadowDOM
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollbarComponent
|
ScrollbarComponent
|
||||||
ref: 'horizontalScrollbar'
|
ref: 'horizontalScrollbar'
|
||||||
className: 'horizontal-scrollbar'
|
className: 'horizontal-scrollbar'
|
||||||
orientation: 'horizontal'
|
orientation: 'horizontal'
|
||||||
|
presenter: @presenter
|
||||||
onScroll: @onHorizontalScroll
|
onScroll: @onHorizontalScroll
|
||||||
scrollLeft: scrollLeft
|
|
||||||
scrollWidth: scrollWidth
|
|
||||||
visible: horizontallyScrollable
|
|
||||||
scrollableInOppositeDirection: verticallyScrollable
|
|
||||||
verticalScrollbarWidth: verticalScrollbarWidth
|
|
||||||
horizontalScrollbarHeight: horizontalScrollbarHeight
|
|
||||||
useHardwareAcceleration: @useHardwareAcceleration
|
useHardwareAcceleration: @useHardwareAcceleration
|
||||||
|
|
||||||
ScrollbarComponent
|
ScrollbarComponent
|
||||||
ref: 'verticalScrollbar'
|
ref: 'verticalScrollbar'
|
||||||
className: 'vertical-scrollbar'
|
className: 'vertical-scrollbar'
|
||||||
orientation: 'vertical'
|
orientation: 'vertical'
|
||||||
|
presenter: @presenter
|
||||||
onScroll: @onVerticalScroll
|
onScroll: @onVerticalScroll
|
||||||
scrollTop: scrollTop
|
|
||||||
scrollHeight: scrollHeight
|
|
||||||
visible: verticallyScrollable
|
|
||||||
scrollableInOppositeDirection: horizontallyScrollable
|
|
||||||
verticalScrollbarWidth: verticalScrollbarWidth
|
|
||||||
horizontalScrollbarHeight: horizontalScrollbarHeight
|
|
||||||
useHardwareAcceleration: @useHardwareAcceleration
|
useHardwareAcceleration: @useHardwareAcceleration
|
||||||
|
|
||||||
# Also used to measure the height/width of scrollbars after the initial render
|
# Also used to measure the height/width of scrollbars after the initial render
|
||||||
ScrollbarCornerComponent
|
ScrollbarCornerComponent
|
||||||
ref: 'scrollbarCorner'
|
ref: 'scrollbarCorner'
|
||||||
visible: horizontallyScrollable and verticallyScrollable
|
presenter: @presenter
|
||||||
measuringScrollbars: @measuringScrollbars
|
measuringScrollbars: @measuringScrollbars
|
||||||
height: horizontalScrollbarHeight
|
|
||||||
width: verticalScrollbarWidth
|
|
||||||
|
|
||||||
getPageRows: ->
|
|
||||||
{editor} = @props
|
|
||||||
Math.max(1, Math.ceil(editor.getHeight() / editor.getLineHeightInPixels()))
|
|
||||||
|
|
||||||
getInitialState: -> {}
|
getInitialState: -> {}
|
||||||
|
|
||||||
@@ -167,11 +109,23 @@ TextEditorComponent = React.createClass
|
|||||||
lineOverdrawMargin: 15
|
lineOverdrawMargin: 15
|
||||||
|
|
||||||
componentWillMount: ->
|
componentWillMount: ->
|
||||||
@pendingChanges = []
|
|
||||||
@props.editor.manageScrollPosition = true
|
@props.editor.manageScrollPosition = true
|
||||||
@observeConfig()
|
@observeConfig()
|
||||||
@setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))
|
@setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))
|
||||||
|
|
||||||
|
{editor, lineOverdrawMargin, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
|
||||||
|
|
||||||
|
@presenter = new TextEditorPresenter
|
||||||
|
model: editor
|
||||||
|
scrollTop: editor.getScrollTop()
|
||||||
|
scrollLeft: editor.getScrollLeft()
|
||||||
|
lineOverdrawMargin: lineOverdrawMargin
|
||||||
|
cursorBlinkPeriod: cursorBlinkPeriod
|
||||||
|
cursorBlinkResumeDelay: cursorBlinkResumeDelay
|
||||||
|
stoppedScrollingDelay: 200
|
||||||
|
@presenter.onDidUpdateState(@requestUpdate)
|
||||||
|
|
||||||
|
|
||||||
componentDidMount: ->
|
componentDidMount: ->
|
||||||
{editor, stylesElement} = @props
|
{editor, stylesElement} = @props
|
||||||
|
|
||||||
@@ -202,7 +156,6 @@ TextEditorComponent = React.createClass
|
|||||||
componentDidUpdate: (prevProps, prevState) ->
|
componentDidUpdate: (prevProps, prevState) ->
|
||||||
cursorMoved = @cursorMoved
|
cursorMoved = @cursorMoved
|
||||||
selectionChanged = @selectionChanged
|
selectionChanged = @selectionChanged
|
||||||
@pendingChanges.length = 0
|
|
||||||
@cursorMoved = false
|
@cursorMoved = false
|
||||||
@selectionChanged = false
|
@selectionChanged = false
|
||||||
|
|
||||||
@@ -215,10 +168,10 @@ TextEditorComponent = React.createClass
|
|||||||
|
|
||||||
becameVisible: ->
|
becameVisible: ->
|
||||||
@updatesPaused = true
|
@updatesPaused = true
|
||||||
|
@measureScrollbars() if @measureScrollbarsWhenShown
|
||||||
@sampleFontStyling()
|
@sampleFontStyling()
|
||||||
@sampleBackgroundColors()
|
@sampleBackgroundColors()
|
||||||
@measureHeightAndWidth()
|
@measureHeightAndWidth()
|
||||||
@measureScrollbars() if @measureScrollbarsWhenShown
|
|
||||||
@measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown
|
@measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown
|
||||||
@remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
|
@remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
|
||||||
@props.editor.setVisible(true)
|
@props.editor.setVisible(true)
|
||||||
@@ -257,13 +210,6 @@ TextEditorComponent = React.createClass
|
|||||||
getTopmostDOMNode: ->
|
getTopmostDOMNode: ->
|
||||||
@props.hostElement
|
@props.hostElement
|
||||||
|
|
||||||
getRenderedRowRange: ->
|
|
||||||
{editor, lineOverdrawMargin} = @props
|
|
||||||
[visibleStartRow, visibleEndRow] = editor.getVisibleRowRange()
|
|
||||||
renderedStartRow = Math.max(0, visibleStartRow - lineOverdrawMargin)
|
|
||||||
renderedEndRow = Math.min(editor.getScreenLineCount(), visibleEndRow + lineOverdrawMargin)
|
|
||||||
[renderedStartRow, renderedEndRow]
|
|
||||||
|
|
||||||
getHiddenInputPosition: ->
|
getHiddenInputPosition: ->
|
||||||
{editor} = @props
|
{editor} = @props
|
||||||
{focused} = @state
|
{focused} = @state
|
||||||
@@ -277,117 +223,13 @@ TextEditorComponent = React.createClass
|
|||||||
left = Math.max(0, Math.min(editor.getWidth() - width, left))
|
left = Math.max(0, Math.min(editor.getWidth() - width, left))
|
||||||
{top, left}
|
{top, left}
|
||||||
|
|
||||||
getCursorScreenRanges: (renderedRowRange) ->
|
|
||||||
{editor} = @props
|
|
||||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
|
||||||
|
|
||||||
cursorScreenRanges = {}
|
|
||||||
for selection in editor.getSelections() when selection.isEmpty()
|
|
||||||
{cursor} = selection
|
|
||||||
screenRange = cursor.getScreenRange()
|
|
||||||
if renderedStartRow <= screenRange.start.row < renderedEndRow
|
|
||||||
cursorScreenRanges[cursor.id] = screenRange
|
|
||||||
cursorScreenRanges
|
|
||||||
|
|
||||||
getCursorPixelRects: (renderedRowRange) ->
|
|
||||||
{editor} = @props
|
|
||||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
|
||||||
|
|
||||||
cursorPixelRects = {}
|
|
||||||
for selection in editor.getSelections() when selection.isEmpty()
|
|
||||||
{cursor} = selection
|
|
||||||
screenRange = cursor.getScreenRange()
|
|
||||||
if renderedStartRow <= screenRange.start.row < renderedEndRow
|
|
||||||
cursorPixelRects[cursor.id] = editor.pixelRectForScreenRange(screenRange)
|
|
||||||
cursorPixelRects
|
|
||||||
|
|
||||||
getLineDecorations: (decorationsByMarkerId) ->
|
|
||||||
{editor} = @props
|
|
||||||
return {} if editor.isMini()
|
|
||||||
|
|
||||||
decorationsByScreenRow = {}
|
|
||||||
for markerId, decorations of decorationsByMarkerId
|
|
||||||
marker = editor.getMarker(markerId)
|
|
||||||
screenRange = null
|
|
||||||
headScreenRow = null
|
|
||||||
if marker.isValid()
|
|
||||||
for decoration in decorations
|
|
||||||
if decoration.isType('line-number') or decoration.isType('line')
|
|
||||||
decorationParams = decoration.getProperties()
|
|
||||||
screenRange ?= marker.getScreenRange()
|
|
||||||
headScreenRow ?= marker.getHeadScreenPosition().row
|
|
||||||
startRow = screenRange.start.row
|
|
||||||
endRow = screenRange.end.row
|
|
||||||
endRow-- if not screenRange.isEmpty() and screenRange.end.column == 0
|
|
||||||
for screenRow in [startRow..endRow]
|
|
||||||
continue if decorationParams.onlyHead and screenRow isnt headScreenRow
|
|
||||||
if screenRange.isEmpty()
|
|
||||||
continue if decorationParams.onlyNonEmpty
|
|
||||||
else
|
|
||||||
continue if decorationParams.onlyEmpty
|
|
||||||
|
|
||||||
decorationsByScreenRow[screenRow] ?= {}
|
|
||||||
decorationsByScreenRow[screenRow][decoration.id] = decorationParams
|
|
||||||
|
|
||||||
decorationsByScreenRow
|
|
||||||
|
|
||||||
getHighlightDecorations: (decorationsByMarkerId) ->
|
|
||||||
{editor} = @props
|
|
||||||
filteredDecorations = {}
|
|
||||||
for markerId, decorations of decorationsByMarkerId
|
|
||||||
marker = editor.getMarker(markerId)
|
|
||||||
screenRange = marker.getScreenRange()
|
|
||||||
if marker.isValid() and not screenRange.isEmpty()
|
|
||||||
for decoration in decorations
|
|
||||||
if decoration.isType('highlight')
|
|
||||||
decorationParams = decoration.getProperties()
|
|
||||||
filteredDecorations[markerId] ?=
|
|
||||||
id: markerId
|
|
||||||
startPixelPosition: editor.pixelPositionForScreenPosition(screenRange.start, true)
|
|
||||||
endPixelPosition: editor.pixelPositionForScreenPosition(screenRange.end, true)
|
|
||||||
decorations: []
|
|
||||||
filteredDecorations[markerId].decorations.push decorationParams
|
|
||||||
filteredDecorations
|
|
||||||
|
|
||||||
getOverlayDecorations: (decorationsByMarkerId) ->
|
|
||||||
{editor} = @props
|
|
||||||
filteredDecorations = {}
|
|
||||||
for markerId, decorations of decorationsByMarkerId
|
|
||||||
marker = editor.getMarker(markerId)
|
|
||||||
headScreenPosition = marker.getHeadScreenPosition()
|
|
||||||
tailScreenPosition = marker.getTailScreenPosition()
|
|
||||||
if marker.isValid()
|
|
||||||
for decoration in decorations
|
|
||||||
if decoration.isType('overlay')
|
|
||||||
decorationParams = decoration.getProperties()
|
|
||||||
filteredDecorations[markerId] ?=
|
|
||||||
id: markerId
|
|
||||||
headPixelPosition: editor.pixelPositionForScreenPosition(headScreenPosition, true)
|
|
||||||
tailPixelPosition: editor.pixelPositionForScreenPosition(tailScreenPosition, true)
|
|
||||||
decorations: []
|
|
||||||
filteredDecorations[markerId].decorations.push decorationParams
|
|
||||||
filteredDecorations
|
|
||||||
|
|
||||||
observeEditor: ->
|
observeEditor: ->
|
||||||
{editor} = @props
|
{editor} = @props
|
||||||
@subscribe editor.onDidChange(@onScreenLinesChanged)
|
|
||||||
@subscribe editor.onDidChangeGutterVisible(@updateGutterVisible)
|
@subscribe editor.onDidChangeGutterVisible(@updateGutterVisible)
|
||||||
@subscribe editor.onDidChangeMini(@setMini)
|
@subscribe editor.onDidChangeMini(@setMini)
|
||||||
@subscribe editor.observeGrammar(@onGrammarChanged)
|
@subscribe editor.observeGrammar(@onGrammarChanged)
|
||||||
@subscribe editor.observeCursors(@onCursorAdded)
|
@subscribe editor.observeCursors(@onCursorAdded)
|
||||||
@subscribe editor.observeSelections(@onSelectionAdded)
|
@subscribe editor.observeSelections(@onSelectionAdded)
|
||||||
@subscribe editor.observeDecorations(@onDecorationAdded)
|
|
||||||
@subscribe editor.onDidRemoveDecoration(@onDecorationRemoved)
|
|
||||||
@subscribe editor.onDidChangeCharacterWidths(@onCharacterWidthsChanged)
|
|
||||||
@subscribe editor.onDidChangePlaceholderText(@onPlaceholderTextChanged)
|
|
||||||
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
|
|
||||||
@subscribe editor.$scrollLeft.changes, @requestUpdate
|
|
||||||
@subscribe editor.$verticalScrollbarWidth.changes, @requestUpdate
|
|
||||||
@subscribe editor.$horizontalScrollbarHeight.changes, @requestUpdate
|
|
||||||
@subscribe editor.$height.changes, @requestUpdate
|
|
||||||
@subscribe editor.$width.changes, @requestUpdate
|
|
||||||
@subscribe editor.$defaultCharWidth.changes, @requestUpdate
|
|
||||||
@subscribe editor.$lineHeightInPixels.changes, @requestUpdate
|
|
||||||
|
|
||||||
listenForDOMEvents: ->
|
listenForDOMEvents: ->
|
||||||
node = @getDOMNode()
|
node = @getDOMNode()
|
||||||
@@ -458,7 +300,7 @@ TextEditorComponent = React.createClass
|
|||||||
|
|
||||||
scopeDescriptor = editor.getRootScopeDescriptor()
|
scopeDescriptor = editor.getRootScopeDescriptor()
|
||||||
|
|
||||||
subscriptions.add atom.config.observe 'editor.showIndentGuide', scope: scopeDescriptor, @setShowIndentGuide
|
subscriptions.add atom.config.observe 'editor.showIndentGuide', scope: scopeDescriptor, @requestUpdate
|
||||||
subscriptions.add atom.config.observe 'editor.showLineNumbers', scope: scopeDescriptor, @updateGutterVisible
|
subscriptions.add atom.config.observe 'editor.showLineNumbers', scope: scopeDescriptor, @updateGutterVisible
|
||||||
subscriptions.add atom.config.observe 'editor.scrollSensitivity', scope: scopeDescriptor, @setScrollSensitivity
|
subscriptions.add atom.config.observe 'editor.scrollSensitivity', scope: scopeDescriptor, @setScrollSensitivity
|
||||||
|
|
||||||
@@ -505,7 +347,7 @@ TextEditorComponent = React.createClass
|
|||||||
@requestAnimationFrame =>
|
@requestAnimationFrame =>
|
||||||
pendingScrollTop = @pendingScrollTop
|
pendingScrollTop = @pendingScrollTop
|
||||||
@pendingScrollTop = null
|
@pendingScrollTop = null
|
||||||
@props.editor.setScrollTop(pendingScrollTop)
|
@presenter.setScrollTop(pendingScrollTop)
|
||||||
|
|
||||||
onHorizontalScroll: (scrollLeft) ->
|
onHorizontalScroll: (scrollLeft) ->
|
||||||
{editor} = @props
|
{editor} = @props
|
||||||
@@ -516,7 +358,7 @@ TextEditorComponent = React.createClass
|
|||||||
@pendingScrollLeft = scrollLeft
|
@pendingScrollLeft = scrollLeft
|
||||||
unless animationFramePending
|
unless animationFramePending
|
||||||
@requestAnimationFrame =>
|
@requestAnimationFrame =>
|
||||||
@props.editor.setScrollLeft(@pendingScrollLeft)
|
@presenter.setScrollLeft(@pendingScrollLeft)
|
||||||
@pendingScrollLeft = null
|
@pendingScrollLeft = null
|
||||||
|
|
||||||
onMouseWheel: (event) ->
|
onMouseWheel: (event) ->
|
||||||
@@ -537,15 +379,13 @@ TextEditorComponent = React.createClass
|
|||||||
if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)
|
if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)
|
||||||
# Scrolling horizontally
|
# Scrolling horizontally
|
||||||
previousScrollLeft = editor.getScrollLeft()
|
previousScrollLeft = editor.getScrollLeft()
|
||||||
editor.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity))
|
@presenter.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity))
|
||||||
event.preventDefault() unless previousScrollLeft is editor.getScrollLeft()
|
event.preventDefault() unless previousScrollLeft is editor.getScrollLeft()
|
||||||
else
|
else
|
||||||
# Scrolling vertically
|
# Scrolling vertically
|
||||||
@mouseWheelScreenRow = @screenRowForNode(event.target)
|
@presenter.setMouseWheelScreenRow(@screenRowForNode(event.target))
|
||||||
@clearMouseWheelScreenRowAfterDelay ?= debounce(@clearMouseWheelScreenRow, @mouseWheelScreenRowClearDelay)
|
previousScrollTop = @presenter.scrollTop
|
||||||
@clearMouseWheelScreenRowAfterDelay()
|
@presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity))
|
||||||
previousScrollTop = editor.getScrollTop()
|
|
||||||
editor.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity))
|
|
||||||
event.preventDefault() unless previousScrollTop is editor.getScrollTop()
|
event.preventDefault() unless previousScrollTop is editor.getScrollTop()
|
||||||
|
|
||||||
onScrollViewScroll: ->
|
onScrollViewScroll: ->
|
||||||
@@ -682,11 +522,6 @@ TextEditorComponent = React.createClass
|
|||||||
@sampleBackgroundColors()
|
@sampleBackgroundColors()
|
||||||
@remeasureCharacterWidths()
|
@remeasureCharacterWidths()
|
||||||
|
|
||||||
onScreenLinesChanged: (change) ->
|
|
||||||
{editor} = @props
|
|
||||||
@pendingChanges.push(change)
|
|
||||||
@requestUpdate() if editor.intersectsVisibleRowRange(change.start, change.end + 1) # TODO: Use closed-open intervals for change events
|
|
||||||
|
|
||||||
onSelectionAdded: (selection) ->
|
onSelectionAdded: (selection) ->
|
||||||
{editor} = @props
|
{editor} = @props
|
||||||
|
|
||||||
@@ -705,21 +540,6 @@ TextEditorComponent = React.createClass
|
|||||||
@selectionChanged = true
|
@selectionChanged = true
|
||||||
@requestUpdate()
|
@requestUpdate()
|
||||||
|
|
||||||
onScrollTopChanged: ->
|
|
||||||
@scrollingVertically = true
|
|
||||||
@requestUpdate()
|
|
||||||
@onStoppedScrollingAfterDelay ?= debounce(@onStoppedScrolling, 200)
|
|
||||||
@onStoppedScrollingAfterDelay()
|
|
||||||
|
|
||||||
onStoppedScrolling: ->
|
|
||||||
return unless @isMounted()
|
|
||||||
|
|
||||||
@scrollingVertically = false
|
|
||||||
@mouseWheelScreenRow = null
|
|
||||||
@requestUpdate()
|
|
||||||
|
|
||||||
onStoppedScrollingAfterDelay: null # created lazily
|
|
||||||
|
|
||||||
onCursorAdded: (cursor) ->
|
onCursorAdded: (cursor) ->
|
||||||
@subscribe cursor.onDidChangePosition @onCursorMoved
|
@subscribe cursor.onDidChangePosition @onCursorMoved
|
||||||
|
|
||||||
@@ -727,23 +547,6 @@ TextEditorComponent = React.createClass
|
|||||||
@cursorMoved = true
|
@cursorMoved = true
|
||||||
@requestUpdate()
|
@requestUpdate()
|
||||||
|
|
||||||
onDecorationAdded: (decoration) ->
|
|
||||||
@subscribe decoration.onDidChangeProperties(@onDecorationChanged)
|
|
||||||
@subscribe decoration.getMarker().onDidChange(@onDecorationChanged)
|
|
||||||
@requestUpdate()
|
|
||||||
|
|
||||||
onDecorationChanged: ->
|
|
||||||
@requestUpdate()
|
|
||||||
|
|
||||||
onDecorationRemoved: ->
|
|
||||||
@requestUpdate()
|
|
||||||
|
|
||||||
onCharacterWidthsChanged: (@scopedCharacterWidthsChangeCount) ->
|
|
||||||
@requestUpdate()
|
|
||||||
|
|
||||||
onPlaceholderTextChanged: ->
|
|
||||||
@requestUpdate()
|
|
||||||
|
|
||||||
handleDragUntilMouseUp: (event, dragHandler) ->
|
handleDragUntilMouseUp: (event, dragHandler) ->
|
||||||
{editor} = @props
|
{editor} = @props
|
||||||
dragging = false
|
dragging = false
|
||||||
@@ -840,20 +643,19 @@ TextEditorComponent = React.createClass
|
|||||||
{height} = hostElement.style
|
{height} = hostElement.style
|
||||||
|
|
||||||
if position is 'absolute' or height
|
if position is 'absolute' or height
|
||||||
if @autoHeight
|
@presenter.setAutoHeight(false)
|
||||||
@autoHeight = false
|
height = hostElement.offsetHeight
|
||||||
@forceUpdate() if not @updatesPaused and @canUpdate()
|
if height > 0
|
||||||
|
@presenter.setExplicitHeight(height)
|
||||||
clientHeight = scrollViewNode.clientHeight
|
|
||||||
editor.setHeight(clientHeight) if clientHeight > 0
|
|
||||||
else
|
else
|
||||||
editor.setHeight(null)
|
@presenter.setAutoHeight(true)
|
||||||
@autoHeight = true
|
@presenter.setExplicitHeight(null)
|
||||||
|
|
||||||
clientWidth = scrollViewNode.clientWidth
|
clientWidth = scrollViewNode.clientWidth
|
||||||
paddingLeft = parseInt(getComputedStyle(scrollViewNode).paddingLeft)
|
paddingLeft = parseInt(getComputedStyle(scrollViewNode).paddingLeft)
|
||||||
clientWidth -= paddingLeft
|
clientWidth -= paddingLeft
|
||||||
editor.setWidth(clientWidth) if clientWidth > 0
|
if clientWidth > 0
|
||||||
|
@presenter.setContentFrameWidth(clientWidth)
|
||||||
|
|
||||||
sampleFontStyling: ->
|
sampleFontStyling: ->
|
||||||
oldFontSize = @fontSize
|
oldFontSize = @fontSize
|
||||||
@@ -870,18 +672,13 @@ TextEditorComponent = React.createClass
|
|||||||
|
|
||||||
sampleBackgroundColors: (suppressUpdate) ->
|
sampleBackgroundColors: (suppressUpdate) ->
|
||||||
{hostElement} = @props
|
{hostElement} = @props
|
||||||
{showLineNumbers} = @state
|
|
||||||
{backgroundColor} = getComputedStyle(hostElement)
|
{backgroundColor} = getComputedStyle(hostElement)
|
||||||
|
|
||||||
if backgroundColor isnt @backgroundColor
|
@presenter.setBackgroundColor(backgroundColor)
|
||||||
@backgroundColor = backgroundColor
|
|
||||||
@requestUpdate() unless suppressUpdate
|
|
||||||
|
|
||||||
if @refs.gutter?
|
if @refs.gutter?
|
||||||
gutterBackgroundColor = getComputedStyle(@refs.gutter.getDOMNode()).backgroundColor
|
gutterBackgroundColor = getComputedStyle(@refs.gutter.getDOMNode()).backgroundColor
|
||||||
if gutterBackgroundColor isnt @gutterBackgroundColor
|
@presenter.setGutterBackgroundColor(gutterBackgroundColor)
|
||||||
@gutterBackgroundColor = gutterBackgroundColor
|
|
||||||
@requestUpdate() unless suppressUpdate
|
|
||||||
|
|
||||||
measureLineHeightAndDefaultCharWidth: ->
|
measureLineHeightAndDefaultCharWidth: ->
|
||||||
if @isVisible()
|
if @isVisible()
|
||||||
@@ -909,8 +706,8 @@ TextEditorComponent = React.createClass
|
|||||||
width = (cornerNode.offsetWidth - cornerNode.clientWidth) or 15
|
width = (cornerNode.offsetWidth - cornerNode.clientWidth) or 15
|
||||||
height = (cornerNode.offsetHeight - cornerNode.clientHeight) or 15
|
height = (cornerNode.offsetHeight - cornerNode.clientHeight) or 15
|
||||||
|
|
||||||
editor.setVerticalScrollbarWidth(width)
|
@presenter.setVerticalScrollbarWidth(width)
|
||||||
editor.setHorizontalScrollbarHeight(height)
|
@presenter.setHorizontalScrollbarHeight(height)
|
||||||
|
|
||||||
cornerNode.style.display = originalDisplayValue
|
cornerNode.style.display = originalDisplayValue
|
||||||
|
|
||||||
@@ -955,13 +752,6 @@ TextEditorComponent = React.createClass
|
|||||||
horizontalNode.style.display = originalHorizontalDisplayValue
|
horizontalNode.style.display = originalHorizontalDisplayValue
|
||||||
cornerNode.style.display = originalCornerDisplayValue
|
cornerNode.style.display = originalCornerDisplayValue
|
||||||
|
|
||||||
clearMouseWheelScreenRow: ->
|
|
||||||
if @mouseWheelScreenRow?
|
|
||||||
@mouseWheelScreenRow = null
|
|
||||||
@requestUpdate()
|
|
||||||
|
|
||||||
clearMouseWheelScreenRowAfterDelay: null # created lazily
|
|
||||||
|
|
||||||
consolidateSelections: (e) ->
|
consolidateSelections: (e) ->
|
||||||
e.abortKeyBinding() unless @props.editor.consolidateSelections()
|
e.abortKeyBinding() unless @props.editor.consolidateSelections()
|
||||||
|
|
||||||
@@ -995,7 +785,7 @@ TextEditorComponent = React.createClass
|
|||||||
@sampleFontStyling()
|
@sampleFontStyling()
|
||||||
|
|
||||||
setShowIndentGuide: (showIndentGuide) ->
|
setShowIndentGuide: (showIndentGuide) ->
|
||||||
@setState({showIndentGuide})
|
atom.config.set("editor.showIndentGuide", showIndentGuide)
|
||||||
|
|
||||||
setMini: ->
|
setMini: ->
|
||||||
@updateGutterVisible()
|
@updateGutterVisible()
|
||||||
|
|||||||
@@ -0,0 +1,831 @@
|
|||||||
|
{CompositeDisposable, Emitter} = require 'event-kit'
|
||||||
|
{Point, Range} = require 'text-buffer'
|
||||||
|
_ = require 'underscore-plus'
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
class TextEditorPresenter
|
||||||
|
toggleCursorBlinkHandle: null
|
||||||
|
startBlinkingCursorsAfterDelay: null
|
||||||
|
stoppedScrollingTimeoutId: null
|
||||||
|
mouseWheelScreenRow: null
|
||||||
|
|
||||||
|
constructor: (params) ->
|
||||||
|
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft} = params
|
||||||
|
{@horizontalScrollbarHeight, @verticalScrollbarWidth} = params
|
||||||
|
{@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params
|
||||||
|
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay} = params
|
||||||
|
|
||||||
|
@disposables = new CompositeDisposable
|
||||||
|
@emitter = new Emitter
|
||||||
|
@charWidthsByScope = {}
|
||||||
|
@transferMeasurementsToModel()
|
||||||
|
@observeModel()
|
||||||
|
@observeConfig()
|
||||||
|
@buildState()
|
||||||
|
@startBlinkingCursors()
|
||||||
|
|
||||||
|
destroy: ->
|
||||||
|
@disposables.dispose()
|
||||||
|
|
||||||
|
onDidUpdateState: (callback) ->
|
||||||
|
@emitter.on 'did-update-state', callback
|
||||||
|
|
||||||
|
transferMeasurementsToModel: ->
|
||||||
|
@model.setHeight(@explicitHeight) if @explicitHeight?
|
||||||
|
@model.setWidth(@contentFrameWidth) if @contentFrameWidth?
|
||||||
|
@model.setLineHeightInPixels(@lineHeight) if @lineHeight?
|
||||||
|
@model.setDefaultCharWidth(@baseCharacterWidth) if @baseCharacterWidth?
|
||||||
|
@model.setScrollTop(@scrollTop) if @scrollTop?
|
||||||
|
@model.setScrollLeft(@scrollLeft) if @scrollLeft?
|
||||||
|
@model.setVerticalScrollbarWidth(@verticalScrollbarWidth) if @verticalScrollbarWidth?
|
||||||
|
@model.setHorizontalScrollbarHeight(@horizontalScrollbarHeight) if @horizontalScrollbarHeight?
|
||||||
|
|
||||||
|
observeModel: ->
|
||||||
|
@disposables.add @model.onDidChange =>
|
||||||
|
@updateHeightState()
|
||||||
|
@updateVerticalScrollState()
|
||||||
|
@updateHorizontalScrollState()
|
||||||
|
@updateScrollbarsState()
|
||||||
|
@updateContentState()
|
||||||
|
@updateDecorations()
|
||||||
|
@updateLinesState()
|
||||||
|
@updateGutterState()
|
||||||
|
@updateLineNumbersState()
|
||||||
|
@disposables.add @model.onDidChangeGrammar(@updateContentState.bind(this))
|
||||||
|
@disposables.add @model.onDidChangePlaceholderText(@updateContentState.bind(this))
|
||||||
|
@disposables.add @model.onDidChangeMini =>
|
||||||
|
@updateContentState()
|
||||||
|
@updateDecorations()
|
||||||
|
@updateLinesState()
|
||||||
|
@updateLineNumbersState()
|
||||||
|
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
|
||||||
|
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
|
||||||
|
@disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this))
|
||||||
|
@disposables.add @model.onDidChangeScrollLeft(@setScrollLeft.bind(this))
|
||||||
|
@observeDecoration(decoration) for decoration in @model.getDecorations()
|
||||||
|
@observeCursor(cursor) for cursor in @model.getCursors()
|
||||||
|
|
||||||
|
observeConfig: ->
|
||||||
|
@disposables.add atom.config.onDidChange 'editor.showIndentGuide', scope: @model.getRootScopeDescriptor(), @updateContentState.bind(this)
|
||||||
|
|
||||||
|
buildState: ->
|
||||||
|
@state =
|
||||||
|
horizontalScrollbar: {}
|
||||||
|
verticalScrollbar: {}
|
||||||
|
content:
|
||||||
|
scrollingVertically: false
|
||||||
|
blinkCursorsOff: false
|
||||||
|
lines: {}
|
||||||
|
highlights: {}
|
||||||
|
overlays: {}
|
||||||
|
gutter:
|
||||||
|
lineNumbers: {}
|
||||||
|
@updateState()
|
||||||
|
|
||||||
|
updateState: ->
|
||||||
|
@updateHeightState()
|
||||||
|
@updateVerticalScrollState()
|
||||||
|
@updateHorizontalScrollState()
|
||||||
|
@updateScrollbarsState()
|
||||||
|
@updateContentState()
|
||||||
|
@updateDecorations()
|
||||||
|
@updateLinesState()
|
||||||
|
@updateCursorsState()
|
||||||
|
@updateOverlaysState()
|
||||||
|
@updateGutterState()
|
||||||
|
@updateLineNumbersState()
|
||||||
|
|
||||||
|
updateHeightState: ->
|
||||||
|
if @autoHeight
|
||||||
|
@state.height = @computeContentHeight()
|
||||||
|
else
|
||||||
|
@state.height = null
|
||||||
|
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
updateVerticalScrollState: ->
|
||||||
|
scrollHeight = @computeScrollHeight()
|
||||||
|
@state.content.scrollHeight = scrollHeight
|
||||||
|
@state.gutter.scrollHeight = scrollHeight
|
||||||
|
@state.verticalScrollbar.scrollHeight = scrollHeight
|
||||||
|
|
||||||
|
scrollTop = @computeScrollTop()
|
||||||
|
@state.content.scrollTop = scrollTop
|
||||||
|
@state.gutter.scrollTop = scrollTop
|
||||||
|
@state.verticalScrollbar.scrollTop = scrollTop
|
||||||
|
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
updateHorizontalScrollState: ->
|
||||||
|
scrollWidth = @computeScrollWidth()
|
||||||
|
@state.content.scrollWidth = scrollWidth
|
||||||
|
@state.horizontalScrollbar.scrollWidth = scrollWidth
|
||||||
|
|
||||||
|
scrollLeft = @computeScrollLeft()
|
||||||
|
@state.content.scrollLeft = scrollLeft
|
||||||
|
@state.horizontalScrollbar.scrollLeft = scrollLeft
|
||||||
|
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
updateScrollbarsState: ->
|
||||||
|
horizontalScrollbarHeight = @computeHorizontalScrollbarHeight()
|
||||||
|
verticalScrollbarWidth = @computeVerticalScrollbarWidth()
|
||||||
|
|
||||||
|
@state.horizontalScrollbar.visible = horizontalScrollbarHeight > 0
|
||||||
|
@state.horizontalScrollbar.height = @horizontalScrollbarHeight
|
||||||
|
@state.horizontalScrollbar.right = verticalScrollbarWidth
|
||||||
|
|
||||||
|
@state.verticalScrollbar.visible = verticalScrollbarWidth > 0
|
||||||
|
@state.verticalScrollbar.width = @verticalScrollbarWidth
|
||||||
|
@state.verticalScrollbar.bottom = horizontalScrollbarHeight
|
||||||
|
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
updateContentState: ->
|
||||||
|
@state.content.scrollWidth = @computeScrollWidth()
|
||||||
|
@state.content.scrollLeft = @scrollLeft
|
||||||
|
@state.content.indentGuidesVisible = not @model.isMini() and atom.config.get('editor.showIndentGuide', scope: @model.getRootScopeDescriptor())
|
||||||
|
@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'
|
||||||
|
|
||||||
|
updateLinesState: ->
|
||||||
|
return unless @hasRequiredMeasurements()
|
||||||
|
|
||||||
|
visibleLineIds = {}
|
||||||
|
startRow = @computeStartRow()
|
||||||
|
endRow = @computeEndRow()
|
||||||
|
row = startRow
|
||||||
|
while row < endRow
|
||||||
|
line = @model.tokenizedLineForScreenRow(row)
|
||||||
|
visibleLineIds[line.id] = true
|
||||||
|
if @state.content.lines.hasOwnProperty(line.id)
|
||||||
|
@updateLineState(row, line)
|
||||||
|
else
|
||||||
|
@buildLineState(row, line)
|
||||||
|
row++
|
||||||
|
|
||||||
|
if @mouseWheelScreenRow?
|
||||||
|
preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow)
|
||||||
|
visibleLineIds[preservedLine.id] = true
|
||||||
|
|
||||||
|
for id, line of @state.content.lines
|
||||||
|
unless visibleLineIds.hasOwnProperty(id)
|
||||||
|
delete @state.content.lines[id]
|
||||||
|
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
updateLineState: (row, line) ->
|
||||||
|
lineState = @state.content.lines[line.id]
|
||||||
|
lineState.screenRow = row
|
||||||
|
lineState.top = row * @lineHeight
|
||||||
|
lineState.decorationClasses = @lineDecorationClassesForRow(row)
|
||||||
|
|
||||||
|
buildLineState: (row, line) ->
|
||||||
|
@state.content.lines[line.id] =
|
||||||
|
screenRow: row
|
||||||
|
text: line.text
|
||||||
|
tokens: line.tokens
|
||||||
|
isOnlyWhitespace: line.isOnlyWhitespace()
|
||||||
|
endOfLineInvisibles: line.endOfLineInvisibles
|
||||||
|
indentLevel: line.indentLevel
|
||||||
|
tabLength: line.tabLength
|
||||||
|
fold: line.fold
|
||||||
|
top: row * @lineHeight
|
||||||
|
decorationClasses: @lineDecorationClassesForRow(row)
|
||||||
|
|
||||||
|
updateCursorsState: ->
|
||||||
|
@state.content.cursors = {}
|
||||||
|
return unless @hasRequiredMeasurements()
|
||||||
|
|
||||||
|
startRow = @computeStartRow()
|
||||||
|
endRow = @computeEndRow()
|
||||||
|
|
||||||
|
for cursor in @model.getCursors()
|
||||||
|
if cursor.isVisible() and startRow <= cursor.getScreenRow() < endRow
|
||||||
|
pixelRect = @pixelRectForScreenRange(cursor.getScreenRange())
|
||||||
|
pixelRect.width = @baseCharacterWidth if pixelRect.width is 0
|
||||||
|
@state.content.cursors[cursor.id] = pixelRect
|
||||||
|
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
updateOverlaysState: ->
|
||||||
|
return unless @hasRequiredMeasurements()
|
||||||
|
|
||||||
|
visibleDecorationIds = {}
|
||||||
|
|
||||||
|
for decoration in @model.getOverlayDecorations()
|
||||||
|
continue unless decoration.getMarker().isValid()
|
||||||
|
|
||||||
|
{item, position} = decoration.getProperties()
|
||||||
|
if position is 'tail'
|
||||||
|
screenPosition = decoration.getMarker().getTailScreenPosition()
|
||||||
|
else
|
||||||
|
screenPosition = decoration.getMarker().getHeadScreenPosition()
|
||||||
|
|
||||||
|
@state.content.overlays[decoration.id] ?= {item}
|
||||||
|
@state.content.overlays[decoration.id].pixelPosition = @pixelPositionForScreenPosition(screenPosition)
|
||||||
|
visibleDecorationIds[decoration.id] = true
|
||||||
|
|
||||||
|
for id of @state.content.overlays
|
||||||
|
delete @state.content.overlays[id] unless visibleDecorationIds[id]
|
||||||
|
|
||||||
|
@emitter.emit "did-update-state"
|
||||||
|
|
||||||
|
updateGutterState: ->
|
||||||
|
@state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length
|
||||||
|
@state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)"
|
||||||
|
@gutterBackgroundColor
|
||||||
|
else
|
||||||
|
@backgroundColor
|
||||||
|
@emitter.emit "did-update-state"
|
||||||
|
|
||||||
|
updateLineNumbersState: ->
|
||||||
|
startRow = @computeStartRow()
|
||||||
|
endRow = @computeEndRow()
|
||||||
|
visibleLineNumberIds = {}
|
||||||
|
|
||||||
|
if startRow > 0
|
||||||
|
rowBeforeStartRow = startRow - 1
|
||||||
|
lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow)
|
||||||
|
wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow)
|
||||||
|
else
|
||||||
|
lastBufferRow = null
|
||||||
|
wrapCount = 0
|
||||||
|
|
||||||
|
for bufferRow, i in @model.bufferRowsForScreenRows(startRow, endRow - 1)
|
||||||
|
if bufferRow is lastBufferRow
|
||||||
|
wrapCount++
|
||||||
|
id = bufferRow + '-' + wrapCount
|
||||||
|
softWrapped = true
|
||||||
|
else
|
||||||
|
id = bufferRow
|
||||||
|
wrapCount = 0
|
||||||
|
lastBufferRow = bufferRow
|
||||||
|
softWrapped = false
|
||||||
|
|
||||||
|
screenRow = startRow + i
|
||||||
|
top = screenRow * @lineHeight
|
||||||
|
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
|
||||||
|
foldable = @model.isFoldableAtScreenRow(screenRow)
|
||||||
|
|
||||||
|
@state.gutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable}
|
||||||
|
visibleLineNumberIds[id] = true
|
||||||
|
|
||||||
|
if @mouseWheelScreenRow?
|
||||||
|
bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow)
|
||||||
|
wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow)
|
||||||
|
id = bufferRow
|
||||||
|
id += '-' + wrapCount if wrapCount > 0
|
||||||
|
visibleLineNumberIds[id] = true
|
||||||
|
|
||||||
|
for id of @state.gutter.lineNumbers
|
||||||
|
delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id]
|
||||||
|
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
buildHighlightRegions: (screenRange) ->
|
||||||
|
lineHeightInPixels = @lineHeight
|
||||||
|
startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, true)
|
||||||
|
endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, true)
|
||||||
|
spannedRows = screenRange.end.row - screenRange.start.row + 1
|
||||||
|
|
||||||
|
if spannedRows is 1
|
||||||
|
[
|
||||||
|
top: startPixelPosition.top
|
||||||
|
height: lineHeightInPixels
|
||||||
|
left: startPixelPosition.left
|
||||||
|
width: endPixelPosition.left - startPixelPosition.left
|
||||||
|
]
|
||||||
|
else
|
||||||
|
regions = []
|
||||||
|
|
||||||
|
# First row, extending from selection start to the right side of screen
|
||||||
|
regions.push(
|
||||||
|
top: startPixelPosition.top
|
||||||
|
left: startPixelPosition.left
|
||||||
|
height: lineHeightInPixels
|
||||||
|
right: 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Middle rows, extending from left side to right side of screen
|
||||||
|
if spannedRows > 2
|
||||||
|
regions.push(
|
||||||
|
top: startPixelPosition.top + lineHeightInPixels
|
||||||
|
height: endPixelPosition.top - startPixelPosition.top - lineHeightInPixels
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Last row, extending from left side of screen to selection end
|
||||||
|
if screenRange.end.column > 0
|
||||||
|
regions.push(
|
||||||
|
top: endPixelPosition.top
|
||||||
|
height: lineHeightInPixels
|
||||||
|
left: 0
|
||||||
|
width: endPixelPosition.left
|
||||||
|
)
|
||||||
|
|
||||||
|
regions
|
||||||
|
|
||||||
|
computeStartRow: ->
|
||||||
|
startRow = Math.floor(@computeScrollTop() / @lineHeight) - @lineOverdrawMargin
|
||||||
|
Math.max(0, startRow)
|
||||||
|
|
||||||
|
computeEndRow: ->
|
||||||
|
startRow = Math.floor(@computeScrollTop() / @lineHeight)
|
||||||
|
visibleLinesCount = Math.ceil(@computeHeight() / @lineHeight) + 1
|
||||||
|
endRow = startRow + visibleLinesCount + @lineOverdrawMargin
|
||||||
|
Math.min(@model.getScreenLineCount(), endRow)
|
||||||
|
|
||||||
|
computeScrollWidth: ->
|
||||||
|
Math.max(@computeContentWidth(), @contentFrameWidth)
|
||||||
|
|
||||||
|
computeScrollHeight: ->
|
||||||
|
Math.max(@computeContentHeight(), @computeHeight())
|
||||||
|
|
||||||
|
computeContentWidth: ->
|
||||||
|
contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left
|
||||||
|
contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
|
||||||
|
contentWidth
|
||||||
|
|
||||||
|
computeContentHeight: ->
|
||||||
|
@lineHeight * @model.getScreenLineCount()
|
||||||
|
|
||||||
|
computeClientHeight: ->
|
||||||
|
@computeHeight() - @computeHorizontalScrollbarHeight()
|
||||||
|
|
||||||
|
computeClientWidth: ->
|
||||||
|
@contentFrameWidth - @computeVerticalScrollbarWidth()
|
||||||
|
|
||||||
|
computeScrollTop: ->
|
||||||
|
@scrollTop = @constrainScrollTop(@scrollTop)
|
||||||
|
|
||||||
|
constrainScrollTop: (scrollTop) ->
|
||||||
|
if @hasRequiredMeasurements()
|
||||||
|
Math.max(0, Math.min(scrollTop, @computeScrollHeight() - @computeClientHeight()))
|
||||||
|
else
|
||||||
|
Math.max(0, scrollTop) if scrollTop?
|
||||||
|
|
||||||
|
computeScrollLeft: ->
|
||||||
|
@scrollLeft = @constrainScrollLeft(@scrollLeft)
|
||||||
|
|
||||||
|
constrainScrollLeft: (scrollLeft) ->
|
||||||
|
if @hasRequiredMeasurements()
|
||||||
|
Math.max(0, Math.min(scrollLeft, @computeScrollWidth() - @computeClientWidth()))
|
||||||
|
else
|
||||||
|
Math.max(0, scrollLeft) if scrollLeft?
|
||||||
|
|
||||||
|
computeHorizontalScrollbarHeight: ->
|
||||||
|
contentWidth = @computeContentWidth()
|
||||||
|
contentHeight = @computeContentHeight()
|
||||||
|
clientWidthWithoutVerticalScrollbar = @contentFrameWidth
|
||||||
|
clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @verticalScrollbarWidth
|
||||||
|
clientHeightWithoutHorizontalScrollbar = @computeHeight()
|
||||||
|
clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @horizontalScrollbarHeight
|
||||||
|
|
||||||
|
horizontalScrollbarVisible =
|
||||||
|
contentWidth > clientWidthWithoutVerticalScrollbar or
|
||||||
|
contentWidth > clientWidthWithVerticalScrollbar and contentHeight > clientHeightWithoutHorizontalScrollbar
|
||||||
|
|
||||||
|
if horizontalScrollbarVisible
|
||||||
|
@horizontalScrollbarHeight
|
||||||
|
else
|
||||||
|
0
|
||||||
|
|
||||||
|
computeVerticalScrollbarWidth: ->
|
||||||
|
contentWidth = @computeContentWidth()
|
||||||
|
contentHeight = @computeContentHeight()
|
||||||
|
clientWidthWithoutVerticalScrollbar = @contentFrameWidth
|
||||||
|
clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @verticalScrollbarWidth
|
||||||
|
clientHeightWithoutHorizontalScrollbar = @computeHeight()
|
||||||
|
clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @horizontalScrollbarHeight
|
||||||
|
|
||||||
|
verticalScrollbarVisible =
|
||||||
|
contentHeight > clientHeightWithoutHorizontalScrollbar or
|
||||||
|
contentHeight > clientHeightWithHorizontalScrollbar and contentWidth > clientWidthWithoutVerticalScrollbar
|
||||||
|
|
||||||
|
if verticalScrollbarVisible
|
||||||
|
@verticalScrollbarWidth
|
||||||
|
else
|
||||||
|
0
|
||||||
|
|
||||||
|
lineDecorationClassesForRow: (row) ->
|
||||||
|
return null if @model.isMini()
|
||||||
|
|
||||||
|
decorationClasses = null
|
||||||
|
for id, decoration of @lineDecorationsByScreenRow[row]
|
||||||
|
decorationClasses ?= []
|
||||||
|
decorationClasses.push(decoration.getProperties().class)
|
||||||
|
decorationClasses
|
||||||
|
|
||||||
|
lineNumberDecorationClassesForRow: (row) ->
|
||||||
|
return null if @model.isMini()
|
||||||
|
|
||||||
|
decorationClasses = null
|
||||||
|
for id, decoration of @lineNumberDecorationsByScreenRow[row]
|
||||||
|
decorationClasses ?= []
|
||||||
|
decorationClasses.push(decoration.getProperties().class)
|
||||||
|
decorationClasses
|
||||||
|
|
||||||
|
getCursorBlinkPeriod: -> @cursorBlinkPeriod
|
||||||
|
|
||||||
|
getCursorBlinkResumeDelay: -> @cursorBlinkResumeDelay
|
||||||
|
|
||||||
|
hasRequiredMeasurements: ->
|
||||||
|
@lineHeight? and
|
||||||
|
@baseCharacterWidth? and
|
||||||
|
@scrollTop? and
|
||||||
|
@contentFrameWidth? and
|
||||||
|
@scrollLeft? and
|
||||||
|
@verticalScrollbarWidth? and
|
||||||
|
@horizontalScrollbarHeight?
|
||||||
|
|
||||||
|
setScrollTop: (scrollTop) ->
|
||||||
|
scrollTop = @constrainScrollTop(scrollTop)
|
||||||
|
|
||||||
|
unless @scrollTop is scrollTop
|
||||||
|
@scrollTop = scrollTop
|
||||||
|
@model.setScrollTop(scrollTop)
|
||||||
|
@didStartScrolling()
|
||||||
|
@updateVerticalScrollState()
|
||||||
|
@updateDecorations()
|
||||||
|
@updateLinesState()
|
||||||
|
@updateCursorsState()
|
||||||
|
@updateLineNumbersState()
|
||||||
|
|
||||||
|
didStartScrolling: ->
|
||||||
|
if @stoppedScrollingTimeoutId?
|
||||||
|
clearTimeout(@stoppedScrollingTimeoutId)
|
||||||
|
@stoppedScrollingTimeoutId = null
|
||||||
|
@stoppedScrollingTimeoutId = setTimeout(@didStopScrolling.bind(this), @stoppedScrollingDelay)
|
||||||
|
@state.content.scrollingVertically = true
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
didStopScrolling: ->
|
||||||
|
@state.content.scrollingVertically = false
|
||||||
|
if @mouseWheelScreenRow?
|
||||||
|
@mouseWheelScreenRow = null
|
||||||
|
@updateLinesState()
|
||||||
|
@updateLineNumbersState()
|
||||||
|
else
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
setScrollLeft: (scrollLeft) ->
|
||||||
|
scrollLeft = @constrainScrollLeft(scrollLeft)
|
||||||
|
unless @scrollLeft is scrollLeft
|
||||||
|
@scrollLeft = scrollLeft
|
||||||
|
@model.setScrollLeft(scrollLeft)
|
||||||
|
@updateHorizontalScrollState()
|
||||||
|
|
||||||
|
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
|
||||||
|
unless @horizontalScrollbarHeight is horizontalScrollbarHeight
|
||||||
|
@horizontalScrollbarHeight = horizontalScrollbarHeight
|
||||||
|
@model.setHorizontalScrollbarHeight(horizontalScrollbarHeight)
|
||||||
|
@updateScrollbarsState()
|
||||||
|
@updateVerticalScrollState()
|
||||||
|
|
||||||
|
setVerticalScrollbarWidth: (verticalScrollbarWidth) ->
|
||||||
|
unless @verticalScrollbarWidth is verticalScrollbarWidth
|
||||||
|
@verticalScrollbarWidth = verticalScrollbarWidth
|
||||||
|
@model.setVerticalScrollbarWidth(verticalScrollbarWidth)
|
||||||
|
@updateScrollbarsState()
|
||||||
|
@updateHorizontalScrollState()
|
||||||
|
|
||||||
|
setAutoHeight: (autoHeight) ->
|
||||||
|
unless @autoHeight is autoHeight
|
||||||
|
@autoHeight = autoHeight
|
||||||
|
@updateHeightState()
|
||||||
|
|
||||||
|
setExplicitHeight: (explicitHeight) ->
|
||||||
|
unless @explicitHeight is explicitHeight
|
||||||
|
@explicitHeight = explicitHeight
|
||||||
|
@model.setHeight(explicitHeight)
|
||||||
|
@updateVerticalScrollState()
|
||||||
|
@updateScrollbarsState()
|
||||||
|
@updateDecorations()
|
||||||
|
@updateLinesState()
|
||||||
|
@updateCursorsState()
|
||||||
|
@updateLineNumbersState()
|
||||||
|
|
||||||
|
computeHeight: ->
|
||||||
|
@explicitHeight ? @computeContentHeight()
|
||||||
|
|
||||||
|
setContentFrameWidth: (contentFrameWidth) ->
|
||||||
|
unless @contentFrameWidth is contentFrameWidth
|
||||||
|
@contentFrameWidth = contentFrameWidth
|
||||||
|
@model.setWidth(contentFrameWidth)
|
||||||
|
@updateVerticalScrollState()
|
||||||
|
@updateHorizontalScrollState()
|
||||||
|
@updateScrollbarsState()
|
||||||
|
@updateContentState()
|
||||||
|
@updateDecorations()
|
||||||
|
@updateLinesState()
|
||||||
|
|
||||||
|
setBackgroundColor: (backgroundColor) ->
|
||||||
|
unless @backgroundColor is backgroundColor
|
||||||
|
@backgroundColor = backgroundColor
|
||||||
|
@updateContentState()
|
||||||
|
|
||||||
|
setGutterBackgroundColor: (gutterBackgroundColor) ->
|
||||||
|
unless @gutterBackgroundColor is gutterBackgroundColor
|
||||||
|
@gutterBackgroundColor = gutterBackgroundColor
|
||||||
|
@updateGutterState()
|
||||||
|
|
||||||
|
setLineHeight: (lineHeight) ->
|
||||||
|
unless @lineHeight is lineHeight
|
||||||
|
@lineHeight = lineHeight
|
||||||
|
@updateHeightState()
|
||||||
|
@updateVerticalScrollState()
|
||||||
|
@updateDecorations()
|
||||||
|
@updateLinesState()
|
||||||
|
@updateCursorsState()
|
||||||
|
@updateLineNumbersState()
|
||||||
|
@updateOverlaysState()
|
||||||
|
|
||||||
|
setMouseWheelScreenRow: (mouseWheelScreenRow) ->
|
||||||
|
unless @mouseWheelScreenRow is mouseWheelScreenRow
|
||||||
|
@mouseWheelScreenRow = mouseWheelScreenRow
|
||||||
|
@didStartScrolling()
|
||||||
|
|
||||||
|
setBaseCharacterWidth: (baseCharacterWidth) ->
|
||||||
|
unless @baseCharacterWidth is baseCharacterWidth
|
||||||
|
@baseCharacterWidth = baseCharacterWidth
|
||||||
|
@model.setDefaultCharWidth(baseCharacterWidth)
|
||||||
|
@characterWidthsChanged()
|
||||||
|
|
||||||
|
getScopedCharWidth: (scopeNames, char) ->
|
||||||
|
@getScopedCharWidths(scopeNames)[char]
|
||||||
|
|
||||||
|
getScopedCharWidths: (scopeNames) ->
|
||||||
|
scope = @charWidthsByScope
|
||||||
|
for scopeName in scopeNames
|
||||||
|
scope[scopeName] ?= {}
|
||||||
|
scope = scope[scopeName]
|
||||||
|
scope.charWidths ?= {}
|
||||||
|
scope.charWidths
|
||||||
|
|
||||||
|
batchCharacterMeasurement: (fn) ->
|
||||||
|
oldChangeCount = @scopedCharacterWidthsChangeCount
|
||||||
|
@batchingCharacterMeasurement = true
|
||||||
|
fn()
|
||||||
|
@batchingCharacterMeasurement = false
|
||||||
|
@characterWidthsChanged() if oldChangeCount isnt @scopedCharacterWidthsChangeCount
|
||||||
|
|
||||||
|
setScopedCharWidth: (scopeNames, char, width) ->
|
||||||
|
@getScopedCharWidths(scopeNames)[char] = width
|
||||||
|
@scopedCharacterWidthsChangeCount++
|
||||||
|
@characterWidthsChanged() unless @batchingCharacterMeasurement
|
||||||
|
|
||||||
|
characterWidthsChanged: ->
|
||||||
|
@updateHorizontalScrollState()
|
||||||
|
@updateContentState()
|
||||||
|
@updateDecorations()
|
||||||
|
@updateLinesState()
|
||||||
|
@updateCursorsState()
|
||||||
|
@updateOverlaysState()
|
||||||
|
|
||||||
|
clearScopedCharWidths: ->
|
||||||
|
@charWidthsByScope = {}
|
||||||
|
|
||||||
|
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
|
||||||
|
screenPosition = Point.fromObject(screenPosition)
|
||||||
|
screenPosition = @model.clipScreenPosition(screenPosition) if clip
|
||||||
|
|
||||||
|
targetRow = screenPosition.row
|
||||||
|
targetColumn = screenPosition.column
|
||||||
|
baseCharacterWidth = @baseCharacterWidth
|
||||||
|
|
||||||
|
top = targetRow * @lineHeight
|
||||||
|
left = 0
|
||||||
|
column = 0
|
||||||
|
for token in @model.tokenizedLineForScreenRow(targetRow).tokens
|
||||||
|
charWidths = @getScopedCharWidths(token.scopes)
|
||||||
|
|
||||||
|
valueIndex = 0
|
||||||
|
while valueIndex < token.value.length
|
||||||
|
if token.hasPairedCharacter
|
||||||
|
char = token.value.substr(valueIndex, 2)
|
||||||
|
charLength = 2
|
||||||
|
valueIndex += 2
|
||||||
|
else
|
||||||
|
char = token.value[valueIndex]
|
||||||
|
charLength = 1
|
||||||
|
valueIndex++
|
||||||
|
|
||||||
|
return {top, left} if column is targetColumn
|
||||||
|
|
||||||
|
left += charWidths[char] ? baseCharacterWidth unless char is '\0'
|
||||||
|
column += charLength
|
||||||
|
{top, left}
|
||||||
|
|
||||||
|
pixelRectForScreenRange: (screenRange) ->
|
||||||
|
if screenRange.end.row > screenRange.start.row
|
||||||
|
top = @pixelPositionForScreenPosition(screenRange.start).top
|
||||||
|
left = 0
|
||||||
|
height = (screenRange.end.row - screenRange.start.row + 1) * @lineHeight
|
||||||
|
width = @computeScrollWidth()
|
||||||
|
else
|
||||||
|
{top, left} = @pixelPositionForScreenPosition(screenRange.start, false)
|
||||||
|
height = @lineHeight
|
||||||
|
width = @pixelPositionForScreenPosition(screenRange.end, false).left - left
|
||||||
|
|
||||||
|
{top, left, width, height}
|
||||||
|
|
||||||
|
observeDecoration: (decoration) ->
|
||||||
|
decorationDisposables = new CompositeDisposable
|
||||||
|
decorationDisposables.add decoration.getMarker().onDidChange(@decorationMarkerDidChange.bind(this, decoration))
|
||||||
|
if decoration.isType('highlight')
|
||||||
|
decorationDisposables.add decoration.onDidChangeProperties(@updateHighlightState.bind(this, decoration))
|
||||||
|
decorationDisposables.add decoration.onDidFlash(@highlightDidFlash.bind(this, decoration))
|
||||||
|
decorationDisposables.add decoration.onDidDestroy =>
|
||||||
|
@disposables.remove(decorationDisposables)
|
||||||
|
decorationDisposables.dispose()
|
||||||
|
@didDestroyDecoration(decoration)
|
||||||
|
@disposables.add(decorationDisposables)
|
||||||
|
|
||||||
|
decorationMarkerDidChange: (decoration, change) ->
|
||||||
|
if decoration.isType('line') or decoration.isType('line-number')
|
||||||
|
intersectsVisibleRowRange = false
|
||||||
|
startRow = @computeStartRow()
|
||||||
|
endRow = @computeEndRow()
|
||||||
|
oldRange = new Range(change.oldTailScreenPosition, change.oldHeadScreenPosition)
|
||||||
|
newRange = new Range(change.newTailScreenPosition, change.newHeadScreenPosition)
|
||||||
|
|
||||||
|
if oldRange.intersectsRowRange(startRow, endRow - 1)
|
||||||
|
@removeFromLineDecorationCaches(decoration, oldRange)
|
||||||
|
intersectsVisibleRowRange = true
|
||||||
|
|
||||||
|
if newRange.intersectsRowRange(startRow, endRow - 1)
|
||||||
|
@addToLineDecorationCaches(decoration, newRange)
|
||||||
|
intersectsVisibleRowRange = true
|
||||||
|
|
||||||
|
if intersectsVisibleRowRange
|
||||||
|
@updateLinesState() if decoration.isType('line')
|
||||||
|
@updateLineNumbersState() if decoration.isType('line-number')
|
||||||
|
|
||||||
|
if decoration.isType('highlight')
|
||||||
|
@updateHighlightState(decoration)
|
||||||
|
|
||||||
|
if decoration.isType('overlay')
|
||||||
|
@updateOverlaysState()
|
||||||
|
|
||||||
|
didDestroyDecoration: (decoration) ->
|
||||||
|
if decoration.isType('line') or decoration.isType('line-number')
|
||||||
|
@removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
|
||||||
|
@updateLinesState() if decoration.isType('line')
|
||||||
|
@updateLineNumbersState() if decoration.isType('line-number')
|
||||||
|
if decoration.isType('highlight')
|
||||||
|
@updateHighlightState(decoration)
|
||||||
|
if decoration.isType('overlay')
|
||||||
|
@updateOverlaysState()
|
||||||
|
|
||||||
|
highlightDidFlash: (decoration) ->
|
||||||
|
flash = decoration.consumeNextFlash()
|
||||||
|
if decorationState = @state.content.highlights[decoration.id]
|
||||||
|
decorationState.flashCount++
|
||||||
|
decorationState.flashClass = flash.class
|
||||||
|
decorationState.flashDuration = flash.duration
|
||||||
|
@emitter.emit "did-update-state"
|
||||||
|
|
||||||
|
didAddDecoration: (decoration) ->
|
||||||
|
@observeDecoration(decoration)
|
||||||
|
|
||||||
|
if decoration.isType('line') or decoration.isType('line-number')
|
||||||
|
@addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
|
||||||
|
@updateLinesState() if decoration.isType('line')
|
||||||
|
@updateLineNumbersState() if decoration.isType('line-number')
|
||||||
|
else if decoration.isType('highlight')
|
||||||
|
@updateHighlightState(decoration)
|
||||||
|
else if decoration.isType('overlay')
|
||||||
|
@updateOverlaysState()
|
||||||
|
|
||||||
|
updateDecorations: ->
|
||||||
|
@lineDecorationsByScreenRow = {}
|
||||||
|
@lineNumberDecorationsByScreenRow = {}
|
||||||
|
@highlightDecorationsById = {}
|
||||||
|
|
||||||
|
visibleHighlights = {}
|
||||||
|
startRow = @computeStartRow()
|
||||||
|
endRow = @computeEndRow()
|
||||||
|
return unless 0 <= startRow <= endRow <= Infinity
|
||||||
|
|
||||||
|
for markerId, decorations of @model.decorationsForScreenRowRange(startRow, endRow - 1)
|
||||||
|
range = @model.getMarker(markerId).getScreenRange()
|
||||||
|
for decoration in decorations
|
||||||
|
if decoration.isType('line') or decoration.isType('line-number')
|
||||||
|
@addToLineDecorationCaches(decoration, range)
|
||||||
|
else if decoration.isType('highlight')
|
||||||
|
visibleHighlights[decoration.id] = @updateHighlightState(decoration)
|
||||||
|
|
||||||
|
for id of @state.content.highlights
|
||||||
|
unless visibleHighlights[id]
|
||||||
|
delete @state.content.highlights[id]
|
||||||
|
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
removeFromLineDecorationCaches: (decoration, range) ->
|
||||||
|
for row in [range.start.row..range.end.row] by 1
|
||||||
|
delete @lineDecorationsByScreenRow[row]?[decoration.id]
|
||||||
|
delete @lineNumberDecorationsByScreenRow[row]?[decoration.id]
|
||||||
|
|
||||||
|
addToLineDecorationCaches: (decoration, range) ->
|
||||||
|
marker = decoration.getMarker()
|
||||||
|
properties = decoration.getProperties()
|
||||||
|
|
||||||
|
return unless marker.isValid()
|
||||||
|
|
||||||
|
if range.isEmpty()
|
||||||
|
return if properties.onlyNonEmpty
|
||||||
|
else
|
||||||
|
return if properties.onlyEmpty
|
||||||
|
omitLastRow = range.end.column is 0
|
||||||
|
|
||||||
|
for row in [range.start.row..range.end.row] by 1
|
||||||
|
continue if properties.onlyHead and row isnt marker.getHeadScreenPosition().row
|
||||||
|
continue if omitLastRow and row is range.end.row
|
||||||
|
|
||||||
|
if decoration.isType('line')
|
||||||
|
@lineDecorationsByScreenRow[row] ?= {}
|
||||||
|
@lineDecorationsByScreenRow[row][decoration.id] = decoration
|
||||||
|
|
||||||
|
if decoration.isType('line-number')
|
||||||
|
@lineNumberDecorationsByScreenRow[row] ?= {}
|
||||||
|
@lineNumberDecorationsByScreenRow[row][decoration.id] = decoration
|
||||||
|
|
||||||
|
updateHighlightState: (decoration) ->
|
||||||
|
return unless @hasRequiredMeasurements()
|
||||||
|
|
||||||
|
startRow = @computeStartRow()
|
||||||
|
endRow = @computeEndRow()
|
||||||
|
properties = decoration.getProperties()
|
||||||
|
marker = decoration.getMarker()
|
||||||
|
range = marker.getScreenRange()
|
||||||
|
|
||||||
|
if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(startRow, endRow - 1)
|
||||||
|
delete @state.content.highlights[decoration.id]
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
return
|
||||||
|
|
||||||
|
if range.start.row < startRow
|
||||||
|
range.start.row = startRow
|
||||||
|
range.start.column = 0
|
||||||
|
if range.end.row >= endRow
|
||||||
|
range.end.row = endRow
|
||||||
|
range.end.column = 0
|
||||||
|
|
||||||
|
if range.isEmpty()
|
||||||
|
delete @state.content.highlights[decoration.id]
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
return
|
||||||
|
|
||||||
|
highlightState = @state.content.highlights[decoration.id] ?= {
|
||||||
|
flashCount: 0
|
||||||
|
flashDuration: null
|
||||||
|
flashClass: null
|
||||||
|
}
|
||||||
|
highlightState.class = properties.class
|
||||||
|
highlightState.deprecatedRegionClass = properties.deprecatedRegionClass
|
||||||
|
highlightState.regions = @buildHighlightRegions(range)
|
||||||
|
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
true
|
||||||
|
|
||||||
|
observeCursor: (cursor) ->
|
||||||
|
didChangePositionDisposable = cursor.onDidChangePosition =>
|
||||||
|
@pauseCursorBlinking()
|
||||||
|
@updateCursorsState()
|
||||||
|
|
||||||
|
didChangeVisibilityDisposable = cursor.onDidChangeVisibility(@updateCursorsState.bind(this))
|
||||||
|
|
||||||
|
didDestroyDisposable = cursor.onDidDestroy =>
|
||||||
|
@disposables.remove(didChangePositionDisposable)
|
||||||
|
@disposables.remove(didChangeVisibilityDisposable)
|
||||||
|
@disposables.remove(didDestroyDisposable)
|
||||||
|
@updateCursorsState()
|
||||||
|
|
||||||
|
@disposables.add(didChangePositionDisposable)
|
||||||
|
@disposables.add(didChangeVisibilityDisposable)
|
||||||
|
@disposables.add(didDestroyDisposable)
|
||||||
|
|
||||||
|
didAddCursor: (cursor) ->
|
||||||
|
@observeCursor(cursor)
|
||||||
|
@pauseCursorBlinking()
|
||||||
|
@updateCursorsState()
|
||||||
|
|
||||||
|
startBlinkingCursors: ->
|
||||||
|
@toggleCursorBlinkHandle = setInterval(@toggleCursorBlink.bind(this), @getCursorBlinkPeriod() / 2)
|
||||||
|
|
||||||
|
stopBlinkingCursors: ->
|
||||||
|
clearInterval(@toggleCursorBlinkHandle)
|
||||||
|
|
||||||
|
toggleCursorBlink: ->
|
||||||
|
@state.content.blinkCursorsOff = not @state.content.blinkCursorsOff
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
|
|
||||||
|
pauseCursorBlinking: ->
|
||||||
|
@state.content.blinkCursorsOff = false
|
||||||
|
@stopBlinkingCursors()
|
||||||
|
@startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay())
|
||||||
|
@startBlinkingCursorsAfterDelay()
|
||||||
|
@emitter.emit 'did-update-state'
|
||||||
@@ -283,7 +283,7 @@ class TextEditorView extends View
|
|||||||
|
|
||||||
setShowIndentGuide: (showIndentGuide) ->
|
setShowIndentGuide: (showIndentGuide) ->
|
||||||
deprecate 'This is going away. Use atom.config.set("editor.showIndentGuide", true|false) instead'
|
deprecate 'This is going away. Use atom.config.set("editor.showIndentGuide", true|false) instead'
|
||||||
@component.setShowIndentGuide(showIndentGuide)
|
atom.config.set("editor.showIndentGuide", showIndentGuide)
|
||||||
|
|
||||||
setSoftWrap: (softWrapped) ->
|
setSoftWrap: (softWrapped) ->
|
||||||
deprecate 'Use TextEditor::setSoftWrapped instead. You can get the editor via editorView.getModel()'
|
deprecate 'Use TextEditor::setSoftWrapped instead. You can get the editor via editorView.getModel()'
|
||||||
|
|||||||
@@ -717,9 +717,13 @@ class TextEditor extends Model
|
|||||||
# {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
|
# {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
|
||||||
bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
|
bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
|
||||||
|
|
||||||
|
screenRowForBufferRow: (row) -> @displayBuffer.screenRowForBufferRow(row)
|
||||||
|
|
||||||
# {Delegates to: DisplayBuffer.getMaxLineLength}
|
# {Delegates to: DisplayBuffer.getMaxLineLength}
|
||||||
getMaxScreenLineLength: -> @displayBuffer.getMaxLineLength()
|
getMaxScreenLineLength: -> @displayBuffer.getMaxLineLength()
|
||||||
|
|
||||||
|
getLongestScreenRow: -> @displayBuffer.getLongestScreenRow()
|
||||||
|
|
||||||
# Returns the range for the given buffer row.
|
# Returns the range for the given buffer row.
|
||||||
#
|
#
|
||||||
# * `row` A row {Number}.
|
# * `row` A row {Number}.
|
||||||
@@ -1349,14 +1353,19 @@ class TextEditor extends Model
|
|||||||
getLineDecorations: (propertyFilter) ->
|
getLineDecorations: (propertyFilter) ->
|
||||||
@displayBuffer.getLineDecorations(propertyFilter)
|
@displayBuffer.getLineDecorations(propertyFilter)
|
||||||
|
|
||||||
|
# Soft-deprecated (forgot to deprecated this pre 1.0)
|
||||||
|
getGutterDecorations: (propertyFilter) ->
|
||||||
|
deprecate("Use ::getLineNumberDecorations instead")
|
||||||
|
@getLineNumberDecorations(propertyFilter)
|
||||||
|
|
||||||
# Extended: Get all decorations of type 'line-number'.
|
# Extended: Get all decorations of type 'line-number'.
|
||||||
#
|
#
|
||||||
# * `propertyFilter` (optional) An {Object} containing key value pairs that
|
# * `propertyFilter` (optional) An {Object} containing key value pairs that
|
||||||
# the returned decorations' properties must match.
|
# the returned decorations' properties must match.
|
||||||
#
|
#
|
||||||
# Returns an {Array} of {Decoration}s.
|
# Returns an {Array} of {Decoration}s.
|
||||||
getGutterDecorations: (propertyFilter) ->
|
getLineNumberDecorations: (propertyFilter) ->
|
||||||
@displayBuffer.getGutterDecorations(propertyFilter)
|
@displayBuffer.getLineNumberDecorations(propertyFilter)
|
||||||
|
|
||||||
# Extended: Get all decorations of type 'highlight'.
|
# Extended: Get all decorations of type 'highlight'.
|
||||||
#
|
#
|
||||||
|
|||||||
Referência em uma Nova Issue
Bloquear um usuário