Merge pull request #8905 from atom/as-display-buffer-logical-coordinates

Use logical coordinates in DisplayBuffer
This commit is contained in:
Antonio Scandurra
2015-09-26 18:05:10 +02:00
10 changed files with 1020 additions and 840 deletions

View File

@@ -47,14 +47,6 @@ describe "DisplayBuffer", ->
buffer.insert([0, 0], oneHundredLines)
expect(displayBuffer.getLineCount()).toBe 100 + originalLineCount
it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", ->
displayBuffer.setHeight(50)
displayBuffer.setLineHeightInPixels(10)
displayBuffer.setScrollTop(80)
buffer.delete([[8, 0], [10, 0]])
expect(displayBuffer.getScrollTop()).toBe 60
it "updates the display buffer prior to invoking change handlers registered on the buffer", ->
buffer.onDidChange -> expect(displayBuffer2.tokenizedLineForScreenRow(0).text).toBe "testing"
displayBuffer2 = new DisplayBuffer({buffer, tabLength})
@@ -297,18 +289,6 @@ describe "DisplayBuffer", ->
displayBuffer.setEditorWidthInChars(-1)
expect(displayBuffer.editorWidthInChars).not.toBe -1
it "sets ::scrollLeft to 0 and keeps it there when soft wrapping is enabled", ->
displayBuffer.setDefaultCharWidth(10)
displayBuffer.setWidth(85)
displayBuffer.setSoftWrapped(false)
displayBuffer.setScrollLeft(Infinity)
expect(displayBuffer.getScrollLeft()).toBeGreaterThan 0
displayBuffer.setSoftWrapped(true)
expect(displayBuffer.getScrollLeft()).toBe 0
displayBuffer.setScrollLeft(10)
expect(displayBuffer.getScrollLeft()).toBe 0
describe "primitive folding", ->
beforeEach ->
displayBuffer.destroy()
@@ -738,21 +718,6 @@ describe "DisplayBuffer", ->
expect(displayBuffer.clipScreenPosition([0, 1], clip: 'forward')).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, tabLength], clip: 'forward')).toEqual [0, tabLength]
describe "::screenPositionForPixelPosition(pixelPosition)", ->
it "clips pixel positions above buffer start", ->
displayBuffer.setLineHeightInPixels(20)
expect(displayBuffer.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0]
expect(displayBuffer.screenPositionForPixelPosition(top: -Infinity, left: Infinity)).toEqual [0, 0]
expect(displayBuffer.screenPositionForPixelPosition(top: -1, left: Infinity)).toEqual [0, 0]
expect(displayBuffer.screenPositionForPixelPosition(top: 0, left: Infinity)).toEqual [0, 29]
it "clips pixel positions below buffer end", ->
displayBuffer.setLineHeightInPixels(20)
expect(displayBuffer.screenPositionForPixelPosition(top: Infinity, left: -Infinity)).toEqual [12, 2]
expect(displayBuffer.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2]
expect(displayBuffer.screenPositionForPixelPosition(top: displayBuffer.getHeight() + 1, left: 0)).toEqual [12, 2]
expect(displayBuffer.screenPositionForPixelPosition(top: displayBuffer.getHeight() - 1, left: 0)).toEqual [12, 0]
describe "::screenPositionForBufferPosition(bufferPosition, options)", ->
it "clips the specified buffer position", ->
expect(displayBuffer.screenPositionForBufferPosition([0, 2])).toEqual [0, 2]
@@ -1186,20 +1151,6 @@ describe "DisplayBuffer", ->
expect(marker1.getProperties()).toEqual a: 1, b: 2
expect(marker2.getProperties()).toEqual a: 1, b: 3
describe "Marker::getPixelRange()", ->
it "returns the start and end positions of the marker based on the line height and character widths assigned to the DisplayBuffer", ->
marker = displayBuffer.markScreenRange([[5, 10], [6, 4]])
displayBuffer.setLineHeightInPixels(20)
displayBuffer.setDefaultCharWidth(10)
for char in ['r', 'e', 't', 'u', 'r', 'n']
displayBuffer.setScopedCharWidth(["source.js", "keyword.control.js"], char, 11)
{start, end} = marker.getPixelRange()
expect(start.top).toBe 5 * 20
expect(start.left).toBe (4 * 10) + (6 * 11)
describe 'when there are multiple DisplayBuffers for a buffer', ->
describe 'when a marker is created', ->
it 'the second display buffer will not emit a marker-created event when the marker has been deleted in the first marker-created event', ->
@@ -1254,157 +1205,18 @@ describe "DisplayBuffer", ->
expect(displayBuffer.getDecorations(class: 'two').length).toEqual 0
expect(displayBuffer.getDecorations(class: 'one').length).toEqual 1
describe "::setScrollTop", ->
beforeEach ->
displayBuffer.setLineHeightInPixels(10)
it "disallows negative values", ->
displayBuffer.setHeight(displayBuffer.getScrollHeight() + 100)
expect(displayBuffer.setScrollTop(-10)).toBe 0
expect(displayBuffer.getScrollTop()).toBe 0
it "disallows values that would make ::getScrollBottom() exceed ::getScrollHeight()", ->
displayBuffer.setHeight(50)
maxScrollTop = displayBuffer.getScrollHeight() - displayBuffer.getHeight()
expect(displayBuffer.setScrollTop(maxScrollTop)).toBe maxScrollTop
expect(displayBuffer.getScrollTop()).toBe maxScrollTop
expect(displayBuffer.setScrollTop(maxScrollTop + 50)).toBe maxScrollTop
expect(displayBuffer.getScrollTop()).toBe maxScrollTop
describe "editor.scrollPastEnd", ->
describe "when editor.scrollPastEnd is false", ->
beforeEach ->
atom.config.set("editor.scrollPastEnd", false)
displayBuffer.setLineHeightInPixels(10)
it "does not add the height of the view to the scroll height", ->
lineHeight = displayBuffer.getLineHeightInPixels()
originalScrollHeight = displayBuffer.getScrollHeight()
displayBuffer.setHeight(50)
expect(displayBuffer.getScrollHeight()).toBe originalScrollHeight
describe "when editor.scrollPastEnd is true", ->
beforeEach ->
atom.config.set("editor.scrollPastEnd", true)
displayBuffer.setLineHeightInPixels(10)
it "adds the height of the view to the scroll height", ->
lineHeight = displayBuffer.getLineHeightInPixels()
originalScrollHeight = displayBuffer.getScrollHeight()
displayBuffer.setHeight(50)
expect(displayBuffer.getScrollHeight()).toEqual(originalScrollHeight + displayBuffer.height - (lineHeight * 3))
describe "::setScrollLeft", ->
beforeEach ->
displayBuffer.setLineHeightInPixels(10)
displayBuffer.setDefaultCharWidth(10)
it "disallows negative values", ->
displayBuffer.setWidth(displayBuffer.getScrollWidth() + 100)
expect(displayBuffer.setScrollLeft(-10)).toBe 0
expect(displayBuffer.getScrollLeft()).toBe 0
it "disallows values that would make ::getScrollRight() exceed ::getScrollWidth()", ->
displayBuffer.setWidth(50)
maxScrollLeft = displayBuffer.getScrollWidth() - displayBuffer.getWidth()
expect(displayBuffer.setScrollLeft(maxScrollLeft)).toBe maxScrollLeft
expect(displayBuffer.getScrollLeft()).toBe maxScrollLeft
expect(displayBuffer.setScrollLeft(maxScrollLeft + 50)).toBe maxScrollLeft
expect(displayBuffer.getScrollLeft()).toBe maxScrollLeft
describe "::scrollToScreenPosition(position, [options])", ->
beforeEach ->
displayBuffer.setLineHeightInPixels(10)
displayBuffer.setDefaultCharWidth(10)
displayBuffer.setHorizontalScrollbarHeight(0)
displayBuffer.setHeight(50)
displayBuffer.setWidth(150)
it "sets the scroll top and scroll left so the given screen position is in view", ->
displayBuffer.scrollToScreenPosition([8, 20])
expect(displayBuffer.getScrollBottom()).toBe (9 + displayBuffer.getVerticalScrollMargin()) * 10
expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10
it "triggers ::onDidRequestAutoscroll with the logical coordinates along with the options", ->
scrollSpy = jasmine.createSpy("::onDidRequestAutoscroll")
displayBuffer.onDidRequestAutoscroll(scrollSpy)
displayBuffer.scrollToScreenPosition([8, 20])
expect(displayBuffer.getScrollBottom()).toBe (9 + displayBuffer.getVerticalScrollMargin()) * 10
expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10
displayBuffer.scrollToScreenPosition([8, 20], center: true)
displayBuffer.scrollToScreenPosition([8, 20], center: false, reversed: true)
describe "when the 'center' option is true", ->
it "vertically scrolls to center the given position vertically", ->
displayBuffer.scrollToScreenPosition([8, 20], center: true)
expect(displayBuffer.getScrollTop()).toBe (8 * 10) + 5 - (50 / 2)
expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10
it "does not scroll vertically if the position is already in view", ->
displayBuffer.scrollToScreenPosition([4, 20], center: true)
expect(displayBuffer.getScrollTop()).toBe 0
describe "scroll width", ->
cursorWidth = 1
beforeEach ->
displayBuffer.setDefaultCharWidth(10)
it "recomputes the scroll width when the default character width changes", ->
expect(displayBuffer.getScrollWidth()).toBe 10 * 65 + cursorWidth
displayBuffer.setDefaultCharWidth(12)
expect(displayBuffer.getScrollWidth()).toBe 12 * 65 + cursorWidth
it "recomputes the scroll width when the max line length changes", ->
buffer.insert([6, 12], ' ')
expect(displayBuffer.getScrollWidth()).toBe 10 * 66 + cursorWidth
buffer.delete([[6, 10], [6, 12]], ' ')
expect(displayBuffer.getScrollWidth()).toBe 10 * 64 + cursorWidth
it "recomputes the scroll width when the scoped character widths change", ->
operatorWidth = 20
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '<', operatorWidth)
expect(displayBuffer.getScrollWidth()).toBe 10 * 64 + operatorWidth + cursorWidth
it "recomputes the scroll width when the scoped character widths change in a batch", ->
operatorWidth = 20
displayBuffer.onDidChangeCharacterWidths changedSpy = jasmine.createSpy()
displayBuffer.batchCharacterMeasurement ->
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '<', operatorWidth)
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '?', operatorWidth)
expect(displayBuffer.getScrollWidth()).toBe 10 * 63 + operatorWidth * 2 + cursorWidth
expect(changedSpy.callCount).toBe 1
describe "::getVisibleRowRange()", ->
beforeEach ->
displayBuffer.setLineHeightInPixels(10)
displayBuffer.setHeight(100)
it "returns the first and the last visible rows", ->
displayBuffer.setScrollTop(0)
expect(displayBuffer.getVisibleRowRange()).toEqual [0, 9]
it "includes partially visible rows in the range", ->
displayBuffer.setScrollTop(5)
expect(displayBuffer.getVisibleRowRange()).toEqual [0, 10]
it "returns an empty range when lineHeight is 0", ->
displayBuffer.setLineHeightInPixels(0)
expect(displayBuffer.getVisibleRowRange()).toEqual [0, 0]
it "ends at last buffer row even if there's more space available", ->
displayBuffer.setHeight(150)
displayBuffer.setScrollTop(60)
expect(displayBuffer.getVisibleRowRange()).toEqual [0, 13]
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {})
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: true})
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: false, reversed: true})
describe "::decorateMarker", ->
describe "when decorating gutters", ->

View File

@@ -285,18 +285,18 @@ describe "TextEditorComponent", ->
componentNode.style.width = gutterWidth + (30 * charWidth) + 'px'
component.measureDimensions()
nextAnimationFrame()
expect(editor.getScrollWidth()).toBeGreaterThan scrollViewNode.offsetWidth
expect(wrapperNode.getScrollWidth()).toBeGreaterThan scrollViewNode.offsetWidth
# At the time of writing, using width: 100% to achieve the full-width
# lines caused full-screen repaints after switching away from an editor
# and back again Please ensure you don't cause a performance regression if
# you change this behavior.
editorFullWidth = editor.getScrollWidth() + editor.getVerticalScrollbarWidth()
editorFullWidth = wrapperNode.getScrollWidth() + wrapperNode.getVerticalScrollbarWidth()
for lineNode in lineNodes
expect(lineNode.getBoundingClientRect().width).toBe(editorFullWidth)
componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px'
componentNode.style.width = gutterWidth + wrapperNode.getScrollWidth() + 100 + 'px'
component.measureDimensions()
nextAnimationFrame()
scrollViewWidth = scrollViewNode.offsetWidth
@@ -473,7 +473,7 @@ describe "TextEditorComponent", ->
editor.setText "a line that wraps \n"
editor.setSoftWrapped(true)
nextAnimationFrame()
componentNode.style.width = 16 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
componentNode.style.width = 16 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
component.measureDimensions()
nextAnimationFrame()
@@ -893,7 +893,7 @@ describe "TextEditorComponent", ->
beforeEach ->
editor.setSoftWrapped(true)
nextAnimationFrame()
componentNode.style.width = 16 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
componentNode.style.width = 16 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
component.measureDimensions()
nextAnimationFrame()
@@ -1672,8 +1672,8 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(editor.getCursorScreenPosition()).toEqual [0, 0]
editor.setScrollTop(3 * lineHeightInPixels)
editor.setScrollLeft(3 * charWidth)
wrapperNode.setScrollTop(3 * lineHeightInPixels)
wrapperNode.setScrollLeft(3 * charWidth)
nextAnimationFrame()
expect(inputNode.offsetTop).toBe 0
@@ -1688,8 +1688,8 @@ describe "TextEditorComponent", ->
# In bounds and focused
wrapperNode.focus() # updates via state change
nextAnimationFrame()
expect(inputNode.offsetTop).toBe (5 * lineHeightInPixels) - editor.getScrollTop()
expect(inputNode.offsetLeft).toBe (4 * charWidth) - editor.getScrollLeft()
expect(inputNode.offsetTop).toBe (5 * lineHeightInPixels) - wrapperNode.getScrollTop()
expect(inputNode.offsetLeft).toBe (4 * charWidth) - wrapperNode.getScrollLeft()
# In bounds, not focused
inputNode.blur() # updates via state change
@@ -1753,8 +1753,8 @@ describe "TextEditorComponent", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 10 * charWidth + 'px'
component.measureDimensions()
editor.setScrollTop(3.5 * lineHeightInPixels)
editor.setScrollLeft(2 * charWidth)
wrapperNode.setScrollTop(3.5 * lineHeightInPixels)
wrapperNode.setScrollLeft(2 * charWidth)
nextAnimationFrame()
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([4, 8])))
@@ -1871,33 +1871,33 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
expect(editor.getScrollTop()).toBe(0)
expect(editor.getScrollLeft()).toBe(0)
expect(wrapperNode.getScrollTop()).toBe(0)
expect(wrapperNode.getScrollLeft()).toBe(0)
linesNode.dispatchEvent(buildMouseEvent('mousedown', {clientX: 0, clientY: 0}, which: 1))
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 100, clientY: 50}, which: 1))
nextAnimationFrame()
expect(editor.getScrollTop()).toBe(0)
expect(editor.getScrollLeft()).toBeGreaterThan(0)
expect(wrapperNode.getScrollTop()).toBe(0)
expect(wrapperNode.getScrollLeft()).toBeGreaterThan(0)
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 100, clientY: 100}, which: 1))
nextAnimationFrame()
expect(editor.getScrollTop()).toBeGreaterThan(0)
expect(wrapperNode.getScrollTop()).toBeGreaterThan(0)
previousScrollTop = editor.getScrollTop()
previousScrollLeft = editor.getScrollLeft()
previousScrollTop = wrapperNode.getScrollTop()
previousScrollLeft = wrapperNode.getScrollLeft()
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 10, clientY: 50}, which: 1))
nextAnimationFrame()
expect(editor.getScrollTop()).toBe(previousScrollTop)
expect(editor.getScrollLeft()).toBeLessThan(previousScrollLeft)
expect(wrapperNode.getScrollTop()).toBe(previousScrollTop)
expect(wrapperNode.getScrollLeft()).toBeLessThan(previousScrollLeft)
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 10, clientY: 10}, which: 1))
nextAnimationFrame()
expect(editor.getScrollTop()).toBeLessThan(previousScrollTop)
expect(wrapperNode.getScrollTop()).toBeLessThan(previousScrollTop)
it "stops selecting if the mouse is dragged into the dev tools", ->
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1))
@@ -1990,12 +1990,12 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[5, 6], [12, 2]]
maximalScrollTop = editor.getScrollTop()
maximalScrollTop = wrapperNode.getScrollTop()
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([9, 3]), which: 1))
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[5, 6], [9, 4]]
expect(editor.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression)
expect(wrapperNode.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression)
linesNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenPosition([9, 3]), which: 1))
@@ -2017,12 +2017,12 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [12, 2]]
maximalScrollTop = editor.getScrollTop()
maximalScrollTop = wrapperNode.getScrollTop()
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([8, 4]), which: 1))
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [8, 0]]
expect(editor.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression)
expect(wrapperNode.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression)
linesNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenPosition([9, 3]), which: 1))
@@ -2117,23 +2117,23 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
expect(editor.getScrollTop()).toBe 0
expect(wrapperNode.getScrollTop()).toBe 0
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2)))
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(8)))
nextAnimationFrame()
expect(editor.getScrollTop()).toBeGreaterThan 0
maxScrollTop = editor.getScrollTop()
expect(wrapperNode.getScrollTop()).toBeGreaterThan 0
maxScrollTop = wrapperNode.getScrollTop()
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(10)))
nextAnimationFrame()
expect(editor.getScrollTop()).toBe maxScrollTop
expect(wrapperNode.getScrollTop()).toBe maxScrollTop
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(7)))
nextAnimationFrame()
expect(editor.getScrollTop()).toBeLessThan maxScrollTop
expect(wrapperNode.getScrollTop()).toBeLessThan maxScrollTop
it "stops selecting if a textInput event occurs during the drag", ->
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2)))
@@ -2242,7 +2242,7 @@ describe "TextEditorComponent", ->
editor.setSoftWrapped(true)
nextAnimationFrame()
componentNode.style.width = 21 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
componentNode.style.width = 21 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
component.measureDimensions()
nextAnimationFrame()
@@ -2414,7 +2414,7 @@ describe "TextEditorComponent", ->
expect(verticalScrollbarNode.scrollTop).toBe 0
editor.setScrollTop(10)
wrapperNode.setScrollTop(10)
nextAnimationFrame()
expect(verticalScrollbarNode.scrollTop).toBe 10
@@ -2432,7 +2432,7 @@ describe "TextEditorComponent", ->
expect(horizontalScrollbarNode.scrollLeft).toBe 0
editor.setScrollLeft(100)
wrapperNode.setScrollLeft(100)
nextAnimationFrame()
top = 0
@@ -2447,18 +2447,18 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
expect(editor.getScrollLeft()).toBe 0
expect(wrapperNode.getScrollLeft()).toBe 0
horizontalScrollbarNode.scrollLeft = 100
horizontalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
expect(editor.getScrollLeft()).toBe 100
expect(wrapperNode.getScrollLeft()).toBe 100
it "does not obscure the last line with the horizontal scrollbar", ->
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = 10 * charWidth + 'px'
component.measureDimensions()
editor.setScrollBottom(editor.getScrollHeight())
wrapperNode.setScrollBottom(wrapperNode.getScrollHeight())
nextAnimationFrame()
lastLineNode = component.lineNodeForScreenRow(editor.getLastScreenRow())
bottomOfLastLine = lastLineNode.getBoundingClientRect().bottom
@@ -2477,7 +2477,7 @@ describe "TextEditorComponent", ->
wrapperNode.style.height = 7 * lineHeightInPixels + 'px'
wrapperNode.style.width = 10 * charWidth + 'px'
component.measureDimensions()
editor.setScrollLeft(Infinity)
wrapperNode.setScrollLeft(Infinity)
nextAnimationFrame()
rightOfLongestLine = component.lineNodeForScreenRow(6).querySelector('.line > span:last-child').getBoundingClientRect().right
@@ -2568,7 +2568,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
expect(horizontalScrollbarNode.scrollWidth).toBe editor.getScrollWidth()
expect(horizontalScrollbarNode.scrollWidth).toBe wrapperNode.getScrollWidth()
expect(horizontalScrollbarNode.style.left).toBe '0px'
describe "mousewheel events", ->
@@ -2647,14 +2647,14 @@ describe "TextEditorComponent", ->
expect(component.presenter.mouseWheelScreenRow).toBe null
it "clears the mouseWheelScreenRow after a delay even if the event does not cause scrolling", ->
expect(editor.getScrollTop()).toBe 0
expect(wrapperNode.getScrollTop()).toBe 0
lineNode = componentNode.querySelector('.line')
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 10)
Object.defineProperty(wheelEvent, 'target', get: -> lineNode)
componentNode.dispatchEvent(wheelEvent)
expect(editor.getScrollTop()).toBe 0
expect(wrapperNode.getScrollTop()).toBe 0
expect(component.presenter.mouseWheelScreenRow).toBe 0
advanceClock(component.presenter.stoppedScrollingDelay)
@@ -2699,36 +2699,36 @@ describe "TextEditorComponent", ->
# try to scroll past the top, which is impossible
componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 50))
expect(editor.getScrollTop()).toBe 0
expect(wrapperNode.getScrollTop()).toBe 0
expect(WheelEvent::preventDefault).not.toHaveBeenCalled()
# scroll to the bottom in one huge event
componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -3000))
nextAnimationFrame()
maxScrollTop = editor.getScrollTop()
maxScrollTop = wrapperNode.getScrollTop()
expect(WheelEvent::preventDefault).toHaveBeenCalled()
WheelEvent::preventDefault.reset()
# try to scroll past the bottom, which is impossible
componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -30))
expect(editor.getScrollTop()).toBe maxScrollTop
expect(wrapperNode.getScrollTop()).toBe maxScrollTop
expect(WheelEvent::preventDefault).not.toHaveBeenCalled()
# try to scroll past the left side, which is impossible
componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 50, wheelDeltaY: 0))
expect(editor.getScrollLeft()).toBe 0
expect(wrapperNode.getScrollLeft()).toBe 0
expect(WheelEvent::preventDefault).not.toHaveBeenCalled()
# scroll all the way right
componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -3000, wheelDeltaY: 0))
nextAnimationFrame()
maxScrollLeft = editor.getScrollLeft()
maxScrollLeft = wrapperNode.getScrollLeft()
expect(WheelEvent::preventDefault).toHaveBeenCalled()
WheelEvent::preventDefault.reset()
# try to scroll past the right side, which is impossible
componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -30, wheelDeltaY: 0))
expect(editor.getScrollLeft()).toBe maxScrollLeft
expect(wrapperNode.getScrollLeft()).toBe maxScrollLeft
expect(WheelEvent::preventDefault).not.toHaveBeenCalled()
describe "input events", ->
@@ -3054,7 +3054,7 @@ describe "TextEditorComponent", ->
expect(componentNode.querySelectorAll('.line')).toHaveLength(6)
gutterWidth = componentNode.querySelector('.gutter').offsetWidth
componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
componentNode.style.width = gutterWidth + 14 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px'
atom.views.performDocumentPoll()
nextAnimationFrame()
expect(componentNode.querySelector('.line').textContent).toBe "var quicksort "
@@ -3324,6 +3324,273 @@ describe "TextEditorComponent", ->
expect(line1LeafNodes[0].classList.contains('indent-guide')).toBe false
expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false
describe "autoscroll", ->
beforeEach ->
editor.setVerticalScrollMargin(2)
editor.setHorizontalScrollMargin(2)
component.setLineHeight("10px")
component.setFontSize(17)
component.measureDimensions()
nextAnimationFrame()
wrapperNode.setWidth(55)
wrapperNode.setHeight(55)
component.measureDimensions()
nextAnimationFrame()
component.presenter.setHorizontalScrollbarHeight(0)
component.presenter.setVerticalScrollbarWidth(0)
nextAnimationFrame()
describe "when selecting buffer ranges", ->
it "autoscrolls the selection if it is last unless the 'autoscroll' option is false", ->
expect(wrapperNode.getScrollTop()).toBe 0
editor.setSelectedBufferRange([[5, 6], [6, 8]])
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
expect(wrapperNode.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10
editor.setSelectedBufferRange([[0, 0], [0, 0]])
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
expect(wrapperNode.getScrollLeft()).toBe 0
editor.setSelectedBufferRange([[6, 6], [6, 8]])
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
expect(wrapperNode.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10
describe "when adding selections for buffer ranges", ->
it "autoscrolls to the added selection if needed", ->
editor.addSelectionForBufferRange([[8, 10], [8, 15]])
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe (9 * 10) + (2 * 10)
expect(wrapperNode.getScrollRight()).toBe (15 * 10) + (2 * 10)
describe "when selecting lines containing cursors", ->
it "autoscrolls to the selection", ->
editor.setCursorScreenPosition([5, 6])
nextAnimationFrame()
wrapperNode.scrollToTop()
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.selectLinesContainingCursors()
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
describe "when inserting text", ->
describe "when there are multiple empty selections on different lines", ->
it "autoscrolls to the last cursor", ->
editor.setCursorScreenPosition([1, 2], autoscroll: false)
nextAnimationFrame()
editor.addCursorAtScreenPosition([10, 4], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.insertText('a')
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 75
describe "when scrolled to cursor position", ->
it "scrolls the last cursor into view, centering around the cursor if possible and the 'center' option isn't false", ->
editor.setCursorScreenPosition([8, 8], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
expect(wrapperNode.getScrollLeft()).toBe 0
editor.scrollToCursorPosition()
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe (8.8 * 10) - 30
expect(wrapperNode.getScrollBottom()).toBe (8.3 * 10) + 30
expect(wrapperNode.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10
wrapperNode.setScrollTop(0)
editor.scrollToCursorPosition(center: false)
expect(wrapperNode.getScrollTop()).toBe (7.8 - editor.getVerticalScrollMargin()) * 10
expect(wrapperNode.getScrollBottom()).toBe (9.3 + editor.getVerticalScrollMargin()) * 10
describe "moving cursors", ->
it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", ->
expect(wrapperNode.getScrollTop()).toBe 0
expect(wrapperNode.getScrollBottom()).toBe 5.5 * 10
editor.setCursorScreenPosition([2, 0])
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe 5.5 * 10
editor.moveDown()
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe 6 * 10
editor.moveDown()
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe 7 * 10
it "scrolls up when the last cursor gets closer than ::verticalScrollMargin to the top of the editor", ->
editor.setCursorScreenPosition([11, 0])
nextAnimationFrame()
wrapperNode.setScrollBottom(wrapperNode.getScrollHeight())
nextAnimationFrame()
editor.moveUp()
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe wrapperNode.getScrollHeight()
editor.moveUp()
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 7 * 10
editor.moveUp()
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 6 * 10
it "scrolls right when the last cursor gets closer than ::horizontalScrollMargin to the right of the editor", ->
expect(wrapperNode.getScrollLeft()).toBe 0
expect(wrapperNode.getScrollRight()).toBe 5.5 * 10
editor.setCursorScreenPosition([0, 2])
nextAnimationFrame()
expect(wrapperNode.getScrollRight()).toBe 5.5 * 10
editor.moveRight()
nextAnimationFrame()
expect(wrapperNode.getScrollRight()).toBe 6 * 10
editor.moveRight()
nextAnimationFrame()
expect(wrapperNode.getScrollRight()).toBe 7 * 10
it "scrolls left when the last cursor gets closer than ::horizontalScrollMargin to the left of the editor", ->
wrapperNode.setScrollRight(wrapperNode.getScrollWidth())
nextAnimationFrame()
expect(wrapperNode.getScrollRight()).toBe wrapperNode.getScrollWidth()
editor.setCursorScreenPosition([6, 62], autoscroll: false)
nextAnimationFrame()
editor.moveLeft()
nextAnimationFrame()
expect(wrapperNode.getScrollLeft()).toBe 59 * 10
editor.moveLeft()
nextAnimationFrame()
expect(wrapperNode.getScrollLeft()).toBe 58 * 10
it "scrolls down when inserting lines makes the document longer than the editor's height", ->
editor.setCursorScreenPosition([13, Infinity])
editor.insertNewline()
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe 14 * 10
editor.insertNewline()
nextAnimationFrame()
expect(wrapperNode.getScrollBottom()).toBe 15 * 10
it "autoscrolls to the cursor when it moves due to undo", ->
editor.insertText('abc')
wrapperNode.setScrollTop(Infinity)
nextAnimationFrame()
editor.undo()
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
it "doesn't scroll when the cursor moves into the visible area", ->
editor.setCursorBufferPosition([0, 0])
nextAnimationFrame()
wrapperNode.setScrollTop(40)
nextAnimationFrame()
editor.setCursorBufferPosition([6, 0])
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 40
it "honors the autoscroll option on cursor and selection manipulation methods", ->
expect(wrapperNode.getScrollTop()).toBe 0
editor.addCursorAtScreenPosition([11, 11], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.addCursorAtBufferPosition([11, 11], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.setCursorScreenPosition([11, 11], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.setCursorBufferPosition([11, 11], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.addSelectionForBufferRange([[11, 11], [11, 11]], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.addSelectionForScreenRange([[11, 11], [11, 12]], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.setSelectedBufferRange([[11, 0], [11, 1]], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.setSelectedScreenRange([[11, 0], [11, 6]], autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.clearSelections(autoscroll: false)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.addSelectionForScreenRange([[0, 0], [0, 4]])
nextAnimationFrame()
editor.getCursors()[0].setScreenPosition([11, 11], autoscroll: true)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBeGreaterThan 0
editor.getCursors()[0].setBufferPosition([0, 0], autoscroll: true)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
editor.getSelections()[0].setScreenRange([[11, 0], [11, 4]], autoscroll: true)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBeGreaterThan 0
editor.getSelections()[0].setBufferRange([[0, 0], [0, 4]], autoscroll: true)
nextAnimationFrame()
expect(wrapperNode.getScrollTop()).toBe 0
describe "::screenPositionForPixelPosition(pixelPosition)", ->
it "clips pixel positions above buffer start", ->
expect(component.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0]
expect(component.screenPositionForPixelPosition(top: -Infinity, left: Infinity)).toEqual [0, 0]
expect(component.screenPositionForPixelPosition(top: -1, left: Infinity)).toEqual [0, 0]
expect(component.screenPositionForPixelPosition(top: 0, left: Infinity)).toEqual [0, 29]
it "clips pixel positions below buffer end", ->
expect(component.screenPositionForPixelPosition(top: Infinity, left: -Infinity)).toEqual [12, 2]
expect(component.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2]
expect(component.screenPositionForPixelPosition(top: component.getScrollHeight() + 1, left: 0)).toEqual [12, 2]
expect(component.screenPositionForPixelPosition(top: component.getScrollHeight() - 1, left: 0)).toEqual [12, 0]
describe "::getVisibleRowRange()", ->
beforeEach ->
wrapperNode.style.height = lineHeightInPixels * 8 + "px"
component.measureDimensions()
nextAnimationFrame()
it "returns the first and the last visible rows", ->
component.setScrollTop(0)
nextAnimationFrame()
expect(component.getVisibleRowRange()).toEqual [0, 9]
it "ends at last buffer row even if there's more space available", ->
wrapperNode.style.height = lineHeightInPixels * 13 + "px"
component.measureDimensions()
nextAnimationFrame()
component.setScrollTop(60)
nextAnimationFrame()
expect(component.getVisibleRowRange()).toEqual [0, 13]
describe "middle mouse paste on Linux", ->
originalPlatform = null
@@ -3368,15 +3635,15 @@ describe "TextEditorComponent", ->
clientCoordinatesForScreenPosition = (screenPosition) ->
positionOffset = wrapperNode.pixelPositionForScreenPosition(screenPosition)
scrollViewClientRect = componentNode.querySelector('.scroll-view').getBoundingClientRect()
clientX = scrollViewClientRect.left + positionOffset.left - editor.getScrollLeft()
clientY = scrollViewClientRect.top + positionOffset.top - editor.getScrollTop()
clientX = scrollViewClientRect.left + positionOffset.left - wrapperNode.getScrollLeft()
clientY = scrollViewClientRect.top + positionOffset.top - wrapperNode.getScrollTop()
{clientX, clientY}
clientCoordinatesForScreenRowInGutter = (screenRow) ->
positionOffset = wrapperNode.pixelPositionForScreenPosition([screenRow, Infinity])
gutterClientRect = componentNode.querySelector('.gutter').getBoundingClientRect()
clientX = gutterClientRect.left + positionOffset.left - editor.getScrollLeft()
clientY = gutterClientRect.top + positionOffset.top - editor.getScrollTop()
clientX = gutterClientRect.left + positionOffset.left - wrapperNode.getScrollLeft()
clientY = gutterClientRect.top + positionOffset.top - wrapperNode.getScrollTop()
{clientX, clientY}
lineAndLineNumberHaveClass = (screenRow, klass) ->

View File

@@ -357,6 +357,18 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setScrollLeft(-300)
expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe 0
it "is always 0 when soft wrapping is enabled", ->
presenter = buildPresenter(scrollLeft: 0, verticalScrollbarWidth: 0, contentFrameWidth: 85, baseCharacterWidth: 10)
editor.setSoftWrapped(false)
presenter.setScrollLeft(Infinity)
expect(presenter.getState().content.scrollLeft).toBeGreaterThan 0
editor.setSoftWrapped(true)
expect(presenter.getState().content.scrollLeft).toBe 0
presenter.setScrollLeft(10)
expect(presenter.getState().content.scrollLeft).toBe 0
describe ".verticalScrollbar", ->
describe ".visible", ->
it "is true if the scrollHeight exceeds the computed client height", ->
@@ -504,11 +516,11 @@ describe "TextEditorPresenter", ->
expectValues presenter.getState().hiddenInput, {top: 0, left: 0}
expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43])
expectValues presenter.getState().hiddenInput, {top: 11 * 10 - editor.getScrollTop(), left: 43 * 10 - editor.getScrollLeft()}
expectValues presenter.getState().hiddenInput, {top: 11 * 10 - presenter.getScrollTop(), left: 43 * 10 - presenter.getScrollLeft()}
newCursor = null
expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10])
expectValues presenter.getState().hiddenInput, {top: (6 * 10) - editor.getScrollTop(), left: (10 * 10) - editor.getScrollLeft()}
expectValues presenter.getState().hiddenInput, {top: (6 * 10) - presenter.getScrollTop(), left: (10 * 10) - presenter.getScrollLeft()}
expectStateUpdate presenter, -> newCursor.destroy()
expectValues presenter.getState().hiddenInput, {top: 50 - 10, left: 300 - 10}
@@ -554,6 +566,7 @@ describe "TextEditorPresenter", ->
advanceClock(100)
expect(presenter.getState().content.scrollingVertically).toBe true
presenter.setScrollTop(10)
presenter.getState() # commits scroll position
advanceClock(100)
expect(presenter.getState().content.scrollingVertically).toBe true
expectStateUpdate presenter, -> advanceClock(100)
@@ -666,12 +679,55 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1
describe ".scrollTop", ->
it "changes based on the scroll operation that was performed last", ->
presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe(0)
presenter.setScrollTop(20)
editor.setCursorBufferPosition([5, 0])
expect(presenter.getState().content.scrollTop).toBe(50)
editor.setCursorBufferPosition([8, 0])
presenter.setScrollTop(10)
expect(presenter.getState().content.scrollTop).toBe(10)
it "corresponds to the passed logical coordinates when building the presenter", ->
presenter = buildPresenter(scrollRow: 4, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe(40)
it "tracks the value of ::scrollTop", ->
presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe 10
expectStateUpdate presenter, -> presenter.setScrollTop(50)
expect(presenter.getState().content.scrollTop).toBe 50
it "keeps the model up to date with the corresponding logical coordinates", ->
presenter = buildPresenter(scrollTop: 0, explicitHeight: 20, horizontalScrollbarHeight: 10, lineHeight: 10)
expectStateUpdate presenter, -> presenter.setScrollTop(50)
presenter.getState() # commits scroll position
expect(editor.getScrollRow()).toBe(5)
expectStateUpdate presenter, -> presenter.setScrollTop(57)
presenter.getState() # commits scroll position
expect(editor.getScrollRow()).toBe(6)
it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", ->
presenter = buildPresenter(scrollTop: 80, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 0)
expect(presenter.getState().content.scrollTop).toBe(80)
buffer.deleteRows(10, 9, 8)
expect(presenter.getState().content.scrollTop).toBe(60)
it "is always rounded to the nearest integer", ->
presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe 10
expectStateUpdate presenter, -> presenter.setScrollTop(11.4)
expect(presenter.getState().content.scrollTop).toBe 11
expectStateUpdate presenter, -> presenter.setScrollTop(12.6)
expect(presenter.getState().content.scrollTop).toBe 13
it "scrolls down automatically when the model is changed", ->
presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20)
@@ -689,17 +745,21 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setExplicitHeight(60)
expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight
expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight
expectStateUpdate presenter, -> presenter.setHorizontalScrollbarHeight(5)
expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight
expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight
expectStateUpdate presenter, -> editor.getBuffer().delete([[8, 0], [12, 0]])
expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight
expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight
# Scroll top only gets smaller when needed as dimensions change, never bigger
scrollTopBefore = presenter.getState().verticalScrollbar.scrollTop
expectStateUpdate presenter, -> editor.getBuffer().insert([9, Infinity], '\n\n\n')
expect(presenter.getState().content.scrollTop).toBe scrollTopBefore
expect(presenter.getRealScrollTop()).toBe scrollTopBefore
it "never goes negative", ->
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
@@ -719,30 +779,72 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.scrollTop).toBe presenter.contentHeight - presenter.clientHeight
describe ".scrollLeft", ->
it "changes based on the scroll operation that was performed last", ->
presenter = buildPresenter(scrollLeft: 0, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 10)
expect(presenter.getState().content.scrollLeft).toBe(0)
presenter.setScrollLeft(20)
editor.setCursorBufferPosition([0, 9])
expect(presenter.getState().content.scrollLeft).toBe(90)
editor.setCursorBufferPosition([0, 18])
presenter.setScrollLeft(50)
expect(presenter.getState().content.scrollLeft).toBe(50)
it "corresponds to the passed logical coordinates when building the presenter", ->
presenter = buildPresenter(scrollColumn: 3, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expect(presenter.getState().content.scrollLeft).toBe(30)
it "tracks the value of ::scrollLeft", ->
presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expect(presenter.getState().content.scrollLeft).toBe 10
expectStateUpdate presenter, -> presenter.setScrollLeft(50)
expect(presenter.getState().content.scrollLeft).toBe 50
it "keeps the model up to date with the corresponding logical coordinates", ->
presenter = buildPresenter(scrollLeft: 0, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expectStateUpdate presenter, -> presenter.setScrollLeft(50)
presenter.getState() # commits scroll position
expect(editor.getScrollColumn()).toBe(5)
expectStateUpdate presenter, -> presenter.setScrollLeft(57)
presenter.getState() # commits scroll position
expect(editor.getScrollColumn()).toBe(6)
it "is always rounded to the nearest integer", ->
presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expect(presenter.getState().content.scrollLeft).toBe 10
expectStateUpdate presenter, -> presenter.setScrollLeft(11.4)
expect(presenter.getState().content.scrollLeft).toBe 11
expectStateUpdate presenter, -> presenter.setScrollLeft(12.6)
expect(presenter.getState().content.scrollLeft).toBe 13
it "never exceeds the computed scrollWidth minus the computed clientWidth", ->
presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expectStateUpdate presenter, -> presenter.setScrollLeft(300)
expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth
expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth
expectStateUpdate presenter, -> presenter.setContentFrameWidth(600)
expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth
expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth
expectStateUpdate presenter, -> presenter.setVerticalScrollbarWidth(5)
expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth
expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth
expectStateUpdate presenter, -> editor.getBuffer().delete([[6, 0], [6, Infinity]])
expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth
expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth
# Scroll top only gets smaller when needed as dimensions change, never bigger
scrollLeftBefore = presenter.getState().content.scrollLeft
expectStateUpdate presenter, -> editor.getBuffer().insert([6, 0], new Array(100).join('x'))
expect(presenter.getState().content.scrollLeft).toBe scrollLeftBefore
expect(presenter.getRealScrollLeft()).toBe scrollLeftBefore
it "never goes negative", ->
presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
@@ -1821,7 +1923,8 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, ->
editor.insertNewline()
editor.setScrollTop(scrollTop) # I'm fighting the editor
presenter.setScrollTop(scrollTop) # I'm fighting the editor
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 6 * 10 - scrollTop - itemHeight, left: gutterWidth}
@@ -2098,6 +2201,12 @@ describe "TextEditorPresenter", ->
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 3, softWrapped: true}
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 4, softWrapped: false}
presenter.setContentFrameWidth(500)
expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 5, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 6, softWrapped: false}
describe ".decorationClasses", ->
it "adds decoration classes to the relevant line number state objects, both initially and when decorations change", ->
marker1 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)

View File

@@ -909,118 +909,6 @@ describe "TextEditor", ->
cursor2 = editor.addCursorAtBufferPosition([1, 4])
expect(cursor2.marker).toBe cursor1.marker
describe "autoscroll", ->
beforeEach ->
editor.setVerticalScrollMargin(2)
editor.setHorizontalScrollMargin(2)
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHorizontalScrollbarHeight(0)
editor.setHeight(5.5 * 10)
editor.setWidth(5.5 * 10)
it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", ->
expect(editor.getScrollTop()).toBe 0
expect(editor.getScrollBottom()).toBe 5.5 * 10
editor.setCursorScreenPosition([2, 0])
expect(editor.getScrollBottom()).toBe 5.5 * 10
editor.moveDown()
expect(editor.getScrollBottom()).toBe 6 * 10
editor.moveDown()
expect(editor.getScrollBottom()).toBe 7 * 10
it "scrolls up when the last cursor gets closer than ::verticalScrollMargin to the top of the editor", ->
editor.setCursorScreenPosition([11, 0])
editor.setScrollBottom(editor.getScrollHeight())
editor.moveUp()
expect(editor.getScrollBottom()).toBe editor.getScrollHeight()
editor.moveUp()
expect(editor.getScrollTop()).toBe 7 * 10
editor.moveUp()
expect(editor.getScrollTop()).toBe 6 * 10
it "scrolls right when the last cursor gets closer than ::horizontalScrollMargin to the right of the editor", ->
expect(editor.getScrollLeft()).toBe 0
expect(editor.getScrollRight()).toBe 5.5 * 10
editor.setCursorScreenPosition([0, 2])
expect(editor.getScrollRight()).toBe 5.5 * 10
editor.moveRight()
expect(editor.getScrollRight()).toBe 6 * 10
editor.moveRight()
expect(editor.getScrollRight()).toBe 7 * 10
it "scrolls left when the last cursor gets closer than ::horizontalScrollMargin to the left of the editor", ->
editor.setScrollRight(editor.getScrollWidth())
expect(editor.getScrollRight()).toBe editor.getScrollWidth()
editor.setCursorScreenPosition([6, 62], autoscroll: false)
editor.moveLeft()
expect(editor.getScrollLeft()).toBe 59 * 10
editor.moveLeft()
expect(editor.getScrollLeft()).toBe 58 * 10
it "scrolls down when inserting lines makes the document longer than the editor's height", ->
editor.setCursorScreenPosition([13, Infinity])
editor.insertNewline()
expect(editor.getScrollBottom()).toBe 14 * 10
editor.insertNewline()
expect(editor.getScrollBottom()).toBe 15 * 10
it "autoscrolls to the cursor when it moves due to undo", ->
editor.insertText('abc')
editor.setScrollTop(Infinity)
editor.undo()
expect(editor.getScrollTop()).toBe 0
it "doesn't scroll when the cursor moves into the visible area", ->
editor.setCursorBufferPosition([0, 0])
editor.setScrollTop(40)
expect(editor.getVisibleRowRange()).toEqual([4, 9])
editor.setCursorBufferPosition([6, 0])
expect(editor.getScrollTop()).toBe 40
it "honors the autoscroll option on cursor and selection manipulation methods", ->
expect(editor.getScrollTop()).toBe 0
editor.addCursorAtScreenPosition([11, 11], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.addCursorAtBufferPosition([11, 11], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.setCursorScreenPosition([11, 11], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.setCursorBufferPosition([11, 11], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.addSelectionForBufferRange([[11, 11], [11, 11]], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.addSelectionForScreenRange([[11, 11], [11, 12]], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.setSelectedBufferRange([[11, 0], [11, 1]], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.setSelectedScreenRange([[11, 0], [11, 6]], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.clearSelections(autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.addSelectionForScreenRange([[0, 0], [0, 4]])
editor.getCursors()[0].setScreenPosition([11, 11], autoscroll: true)
expect(editor.getScrollTop()).toBeGreaterThan 0
editor.getCursors()[0].setBufferPosition([0, 0], autoscroll: true)
expect(editor.getScrollTop()).toBe 0
editor.getSelections()[0].setScreenRange([[11, 0], [11, 4]], autoscroll: true)
expect(editor.getScrollTop()).toBeGreaterThan 0
editor.getSelections()[0].setBufferRange([[0, 0], [0, 4]], autoscroll: true)
expect(editor.getScrollTop()).toBe 0
describe '.logCursorScope()', ->
beforeEach ->
spyOn(atom.notifications, 'addInfo')
@@ -1306,20 +1194,6 @@ describe "TextEditor", ->
editor.selectLinesContainingCursors()
expect(editor.getSelectedBufferRange()).toEqual [[1, 0], [4, 0]]
it "autoscrolls to the selection", ->
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(50)
editor.setWidth(50)
editor.setHorizontalScrollbarHeight(0)
editor.setCursorScreenPosition([5, 6])
editor.scrollToTop()
expect(editor.getScrollTop()).toBe 0
editor.selectLinesContainingCursors()
expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
describe ".selectToBeginningOfWord()", ->
it "selects text from cusor position to beginning of word", ->
editor.setCursorScreenPosition [0, 13]
@@ -1572,30 +1446,6 @@ describe "TextEditor", ->
expect(selection1).toBe selection
expect(selection1.getScreenRange()).toEqual [[2, 2], [3, 4]]
describe ".setSelectedBufferRange(range)", ->
it "autoscrolls the selection if it is last unless the 'autoscroll' option is false", ->
editor.setVerticalScrollMargin(2)
editor.setHorizontalScrollMargin(2)
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(70)
editor.setWidth(100)
editor.setHorizontalScrollbarHeight(0)
expect(editor.getScrollTop()).toBe 0
editor.setSelectedBufferRange([[5, 6], [6, 8]])
expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
expect(editor.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10
editor.setSelectedBufferRange([[0, 0], [0, 0]])
expect(editor.getScrollTop()).toBe 0
expect(editor.getScrollLeft()).toBe 0
editor.setSelectedBufferRange([[6, 6], [6, 8]])
expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
expect(editor.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10
describe ".selectMarker(marker)", ->
describe "if the marker is valid", ->
it "selects the marker's range and returns the selected range", ->
@@ -1615,17 +1465,6 @@ describe "TextEditor", ->
editor.addSelectionForBufferRange([[3, 4], [5, 6]])
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 0]], [[3, 4], [5, 6]]]
it "autoscrolls to the added selection if needed", ->
editor.setVerticalScrollMargin(2)
editor.setHorizontalScrollMargin(2)
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(80)
editor.setWidth(100)
editor.addSelectionForBufferRange([[8, 10], [8, 15]])
expect(editor.getScrollBottom()).toBe (9 * 10) + (2 * 10)
expect(editor.getScrollRight()).toBe (15 * 10) + (2 * 10)
describe ".addSelectionBelow()", ->
describe "when the selection is non-empty", ->
it "selects the same region of the line below current selections if possible", ->
@@ -1976,16 +1815,6 @@ describe "TextEditor", ->
expect(cursor1.getBufferPosition()).toEqual [1, 5]
expect(cursor2.getBufferPosition()).toEqual [2, 7]
it "autoscrolls to the last cursor", ->
editor.setCursorScreenPosition([1, 2])
editor.addCursorAtScreenPosition([10, 4])
editor.setLineHeightInPixels(10)
editor.setHeight(50)
expect(editor.getScrollTop()).toBe 0
editor.insertText('a')
expect(editor.getScrollTop()).toBe 80
describe "when there are multiple non-empty selections", ->
describe "when the selections are on the same line", ->
it "replaces each selection range with the inserted characters", ->
@@ -4547,30 +4376,10 @@ describe "TextEditor", ->
editor.normalizeTabsInBufferRange([[0, 0], [Infinity, Infinity]])
expect(editor.getText()).toBe ' '
describe ".scrollToCursorPosition()", ->
it "scrolls the last cursor into view, centering around the cursor if possible and the 'center' option isn't false", ->
editor.setCursorScreenPosition([8, 8])
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(60)
editor.setWidth(130)
editor.setHorizontalScrollbarHeight(0)
expect(editor.getScrollTop()).toBe 0
expect(editor.getScrollLeft()).toBe 0
editor.scrollToCursorPosition()
expect(editor.getScrollTop()).toBe (8.5 * 10) - 30
expect(editor.getScrollBottom()).toBe (8.5 * 10) + 30
expect(editor.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10
editor.setScrollTop(0)
editor.scrollToCursorPosition(center: false)
expect(editor.getScrollBottom()).toBe (9 + editor.getVerticalScrollMargin()) * 10
describe ".pageUp/Down()", ->
it "moves the cursor down one page length", ->
editor.setLineHeightInPixels(10)
editor.setHeight(50)
editor.setHeight(50, true)
expect(editor.getCursorBufferPosition().row).toBe 0
editor.pageDown()
@@ -4588,8 +4397,7 @@ describe "TextEditor", ->
describe ".selectPageUp/Down()", ->
it "selects one screen height of text up or down", ->
editor.setLineHeightInPixels(10)
editor.setHeight(50)
expect(editor.getScrollHeight()).toBe 130
editor.setHeight(50, true)
expect(editor.getCursorBufferPosition().row).toBe 0
editor.selectPageDown()

View File

@@ -26,11 +26,6 @@ class DisplayBuffer extends Model
defaultCharWidth: null
height: null
width: null
scrollTop: 0
scrollLeft: 0
scrollWidth: 0
verticalScrollbarWidth: 15
horizontalScrollbarHeight: 15
@deserialize: (state) ->
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer)
@@ -99,15 +94,11 @@ class DisplayBuffer extends Model
id: @id
softWrapped: @isSoftWrapped()
editorWidthInChars: @editorWidthInChars
scrollTop: @scrollTop
scrollLeft: @scrollLeft
tokenizedBuffer: @tokenizedBuffer.serialize()
largeFileMode: @largeFileMode
copy: ->
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength(), @largeFileMode})
newDisplayBuffer.setScrollTop(@getScrollTop())
newDisplayBuffer.setScrollLeft(@getScrollLeft())
for marker in @findMarkers(displayBufferId: @id)
marker.copy(displayBufferId: newDisplayBuffer.id)
@@ -134,19 +125,8 @@ class DisplayBuffer extends Model
onDidChangeCharacterWidths: (callback) ->
@emitter.on 'did-change-character-widths', callback
onDidChangeScrollTop: (callback) ->
@emitter.on 'did-change-scroll-top', callback
onDidChangeScrollLeft: (callback) ->
@emitter.on 'did-change-scroll-left', callback
observeScrollTop: (callback) ->
callback(@scrollTop)
@onDidChangeScrollTop(callback)
observeScrollLeft: (callback) ->
callback(@scrollLeft)
@onDidChangeScrollLeft(callback)
onDidRequestAutoscroll: (callback) ->
@emitter.on 'did-request-autoscroll', callback
observeDecorations: (callback) ->
callback(decoration) for decoration in @getDecorations()
@@ -189,105 +169,24 @@ class DisplayBuffer extends Model
setVerticalScrollMargin: (@verticalScrollMargin) -> @verticalScrollMargin
getVerticalScrollMarginInPixels: -> @getVerticalScrollMargin() * @getLineHeightInPixels()
getHorizontalScrollMargin: -> Math.min(@horizontalScrollMargin, Math.floor(((@getWidth() / @getDefaultCharWidth()) - 1) / 2))
setHorizontalScrollMargin: (@horizontalScrollMargin) -> @horizontalScrollMargin
getHorizontalScrollMarginInPixels: -> scrollMarginInPixels = @getHorizontalScrollMargin() * @getDefaultCharWidth()
getHorizontalScrollbarHeight: -> @horizontalScrollbarHeight
setHorizontalScrollbarHeight: (@horizontalScrollbarHeight) -> @horizontalScrollbarHeight
getVerticalScrollbarWidth: -> @verticalScrollbarWidth
setVerticalScrollbarWidth: (@verticalScrollbarWidth) -> @verticalScrollbarWidth
getHeight: ->
if @height?
@height
else
if @horizontallyScrollable()
@getScrollHeight() + @getHorizontalScrollbarHeight()
else
@getScrollHeight()
@height
setHeight: (@height) -> @height
getClientHeight: (reentrant) ->
if @horizontallyScrollable(reentrant)
@getHeight() - @getHorizontalScrollbarHeight()
else
@getHeight()
getClientWidth: (reentrant) ->
if @verticallyScrollable(reentrant)
@getWidth() - @getVerticalScrollbarWidth()
else
@getWidth()
horizontallyScrollable: (reentrant) ->
return false unless @width?
return false if @isSoftWrapped()
if reentrant
@getScrollWidth() > @getWidth()
else
@getScrollWidth() > @getClientWidth(true)
verticallyScrollable: (reentrant) ->
return false unless @height?
if reentrant
@getScrollHeight() > @getHeight()
else
@getScrollHeight() > @getClientHeight(true)
setHeight: (@height) ->
@height
getWidth: ->
if @width?
@width
else
if @verticallyScrollable()
@getScrollWidth() + @getVerticalScrollbarWidth()
else
@getScrollWidth()
@width
setWidth: (newWidth) ->
oldWidth = @width
@width = newWidth
@updateWrappedScreenLines() if newWidth isnt oldWidth and @isSoftWrapped()
@setScrollTop(@getScrollTop()) # Ensure scrollTop is still valid in case horizontal scrollbar disappeared
@width
getScrollTop: -> @scrollTop
setScrollTop: (scrollTop) ->
scrollTop = Math.round(Math.max(0, Math.min(@getMaxScrollTop(), scrollTop)))
unless scrollTop is @scrollTop
@scrollTop = scrollTop
@emitter.emit 'did-change-scroll-top', @scrollTop
@scrollTop
getMaxScrollTop: ->
@getScrollHeight() - @getClientHeight()
getScrollBottom: -> @scrollTop + @getClientHeight()
setScrollBottom: (scrollBottom) ->
@setScrollTop(scrollBottom - @getClientHeight())
@getScrollBottom()
getScrollLeft: -> @scrollLeft
setScrollLeft: (scrollLeft) ->
scrollLeft = Math.round(Math.max(0, Math.min(@getScrollWidth() - @getClientWidth(), scrollLeft)))
unless scrollLeft is @scrollLeft
@scrollLeft = scrollLeft
@emitter.emit 'did-change-scroll-left', @scrollLeft
@scrollLeft
getMaxScrollLeft: ->
@getScrollWidth() - @getClientWidth()
getScrollRight: -> @scrollLeft + @width
setScrollRight: (scrollRight) ->
@setScrollLeft(scrollRight - @width)
@getScrollRight()
getLineHeightInPixels: -> @lineHeightInPixels
setLineHeightInPixels: (@lineHeightInPixels) -> @lineHeightInPixels
@@ -295,7 +194,6 @@ class DisplayBuffer extends Model
setDefaultCharWidth: (defaultCharWidth) ->
if defaultCharWidth isnt @defaultCharWidth
@defaultCharWidth = defaultCharWidth
@computeScrollWidth()
defaultCharWidth
getCursorWidth: -> 1
@@ -324,84 +222,14 @@ class DisplayBuffer extends Model
@characterWidthsChanged() unless @batchingCharacterMeasurement
characterWidthsChanged: ->
@computeScrollWidth()
@emitter.emit 'did-change-character-widths', @scopedCharacterWidthsChangeCount
clearScopedCharWidths: ->
@charWidthsByScope = {}
getScrollHeight: ->
lineHeight = @getLineHeightInPixels()
return 0 unless lineHeight > 0
scrollHeight = @getLineCount() * lineHeight
if @height? and @configSettings.scrollPastEnd
scrollHeight = scrollHeight + @height - (lineHeight * 3)
scrollHeight
getScrollWidth: ->
@scrollWidth
# Returns an {Array} of two numbers representing the first and the last visible rows.
getVisibleRowRange: ->
return [0, 0] unless @getLineHeightInPixels() > 0
startRow = Math.floor(@getScrollTop() / @getLineHeightInPixels())
endRow = Math.ceil((@getScrollTop() + @getHeight()) / @getLineHeightInPixels()) - 1
endRow = Math.min(@getLineCount(), endRow)
[startRow, endRow]
intersectsVisibleRowRange: (startRow, endRow) ->
[visibleStart, visibleEnd] = @getVisibleRowRange()
not (endRow <= visibleStart or visibleEnd <= startRow)
selectionIntersectsVisibleRowRange: (selection) ->
{start, end} = selection.getScreenRange()
@intersectsVisibleRowRange(start.row, end.row + 1)
scrollToScreenRange: (screenRange, options) ->
verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels()
{top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start))
{top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end))
bottom = endTop + endHeight
right = endLeft
if options?.center
desiredScrollCenter = (top + bottom) / 2
unless @getScrollTop() < desiredScrollCenter < @getScrollBottom()
desiredScrollTop = desiredScrollCenter - @getHeight() / 2
desiredScrollBottom = desiredScrollCenter + @getHeight() / 2
else
desiredScrollTop = top - verticalScrollMarginInPixels
desiredScrollBottom = bottom + verticalScrollMarginInPixels
desiredScrollLeft = left - horizontalScrollMarginInPixels
desiredScrollRight = right + horizontalScrollMarginInPixels
if options?.reversed ? true
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
else
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
scrollToScreenRange: (screenRange, options = {}) ->
scrollEvent = {screenRange, options}
@emitter.emit "did-request-autoscroll", scrollEvent
scrollToScreenPosition: (screenPosition, options) ->
@scrollToScreenRange(new Range(screenPosition, screenPosition), options)
@@ -409,19 +237,6 @@ class DisplayBuffer extends Model
scrollToBufferPosition: (bufferPosition, options) ->
@scrollToScreenPosition(@screenPositionForBufferPosition(bufferPosition), options)
pixelRectForScreenRange: (screenRange) ->
if screenRange.end.row > screenRange.start.row
top = @pixelPositionForScreenPosition(screenRange.start).top
left = 0
height = (screenRange.end.row - screenRange.start.row + 1) * @getLineHeightInPixels()
width = @getScrollWidth()
else
{top, left} = @pixelPositionForScreenPosition(screenRange.start, false)
height = @getLineHeightInPixels()
width = @pixelPositionForScreenPosition(screenRange.end, false).left - left
{top, left, width, height}
# Retrieves the current tab length.
#
# Returns a {Number}.
@@ -465,8 +280,7 @@ class DisplayBuffer extends Model
# Returns the editor width in characters for soft wrap.
getEditorWidthInChars: ->
width = @width ? @getScrollWidth()
width -= @getVerticalScrollbarWidth()
width = @getWidth()
if width? and @defaultCharWidth > 0
Math.max(0, Math.floor(width / @defaultCharWidth))
else
@@ -678,80 +492,6 @@ class DisplayBuffer extends Model
end = @bufferPositionForScreenPosition(screenRange.end)
new Range(start, end)
pixelRangeForScreenRange: (screenRange, clip=true) ->
{start, end} = Range.fromObject(screenRange)
{start: @pixelPositionForScreenPosition(start, clip), end: @pixelPositionForScreenPosition(end, clip)}
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = @clipScreenPosition(screenPosition) if clip
targetRow = screenPosition.row
targetColumn = screenPosition.column
defaultCharWidth = @defaultCharWidth
top = targetRow * @lineHeightInPixels
left = 0
column = 0
iterator = @tokenizedLineForScreenRow(targetRow).getTokenIterator()
while iterator.next()
charWidths = @getScopedCharWidths(iterator.getScopes())
valueIndex = 0
value = iterator.getText()
while valueIndex < value.length
if iterator.isPairedCharacter()
char = value
charLength = 2
valueIndex += 2
else
char = value[valueIndex]
charLength = 1
valueIndex++
return {top, left} if column is targetColumn
left += charWidths[char] ? defaultCharWidth unless char is '\0'
column += charLength
{top, left}
screenPositionForPixelPosition: (pixelPosition) ->
targetTop = pixelPosition.top
targetLeft = pixelPosition.left
defaultCharWidth = @defaultCharWidth
row = Math.floor(targetTop / @getLineHeightInPixels())
targetLeft = 0 if row < 0
targetLeft = Infinity if row > @getLastRow()
row = Math.min(row, @getLastRow())
row = Math.max(0, row)
left = 0
column = 0
iterator = @tokenizedLineForScreenRow(row).getTokenIterator()
while iterator.next()
charWidths = @getScopedCharWidths(iterator.getScopes())
value = iterator.getText()
valueIndex = 0
while valueIndex < value.length
if iterator.isPairedCharacter()
char = value
charLength = 2
valueIndex += 2
else
char = value[valueIndex]
charLength = 1
valueIndex++
charWidth = charWidths[char] ? defaultCharWidth
break if targetLeft <= left + (charWidth / 2)
left += charWidth
column += charLength
new Point(row, column)
pixelPositionForBufferPosition: (bufferPosition) ->
@pixelPositionForScreenPosition(@screenPositionForBufferPosition(bufferPosition))
# Gets the number of screen lines.
#
# Returns a {Number}.
@@ -1191,7 +931,6 @@ class DisplayBuffer extends Model
@changeCount = @tokenizedBuffer.changeCount
{start, end, delta, bufferChange} = tokenizedBufferChange
@updateScreenLines(start, end + 1, delta, refreshMarkers: false)
@setScrollTop(Math.min(@getScrollTop(), @getMaxScrollTop())) if delta < 0
updateScreenLines: (startBufferRow, endBufferRow, bufferDelta=0, options={}) ->
return if @largeFileMode
@@ -1296,13 +1035,6 @@ class DisplayBuffer extends Model
@longestScreenRow = screenRow
@maxLineLength = length
@computeScrollWidth() if oldMaxLineLength isnt @maxLineLength
computeScrollWidth: ->
@scrollWidth = @pixelPositionForScreenPosition([@longestScreenRow, @maxLineLength]).left
@scrollWidth += 1 unless @isSoftWrapped()
@setScrollLeft(Math.min(@getScrollLeft(), @getMaxScrollLeft()))
handleBufferMarkerCreated: (textBufferMarker) =>
if textBufferMarker.matchesParams(@getFoldMarkerAttributes())
fold = new Fold(this, textBufferMarker)

View File

@@ -368,6 +368,3 @@ class Marker
@wasValid = isValid
@emitter.emit 'did-change', changeEvent
getPixelRange: ->
@displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false)

View File

@@ -50,8 +50,10 @@ class TextEditorComponent
@presenter = new TextEditorPresenter
model: @editor
scrollTop: @editor.getScrollTop()
scrollLeft: @editor.getScrollLeft()
scrollTop: 0
scrollLeft: 0
scrollRow: @editor.getScrollRow()
scrollColumn: @editor.getScrollColumn()
tileSize: tileSize
cursorBlinkPeriod: @cursorBlinkPeriod
cursorBlinkResumeDelay: @cursorBlinkResumeDelay
@@ -314,7 +316,7 @@ class TextEditorComponent
inputNode.value = event.data if insertedRange
onVerticalScroll: (scrollTop) =>
return if @updateRequested or scrollTop is @editor.getScrollTop()
return if @updateRequested or scrollTop is @presenter.getScrollTop()
animationFramePending = @pendingScrollTop?
@pendingScrollTop = scrollTop
@@ -323,15 +325,17 @@ class TextEditorComponent
pendingScrollTop = @pendingScrollTop
@pendingScrollTop = null
@presenter.setScrollTop(pendingScrollTop)
@presenter.commitPendingScrollTopPosition()
onHorizontalScroll: (scrollLeft) =>
return if @updateRequested or scrollLeft is @editor.getScrollLeft()
return if @updateRequested or scrollLeft is @presenter.getScrollLeft()
animationFramePending = @pendingScrollLeft?
@pendingScrollLeft = scrollLeft
unless animationFramePending
@requestAnimationFrame =>
@presenter.setScrollLeft(@pendingScrollLeft)
@presenter.commitPendingScrollLeftPosition()
@pendingScrollLeft = null
onMouseWheel: (event) =>
@@ -350,14 +354,18 @@ class TextEditorComponent
if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)
# Scrolling horizontally
previousScrollLeft = @presenter.getScrollLeft()
@presenter.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity))
event.preventDefault() unless previousScrollLeft is @presenter.getScrollLeft()
updatedScrollLeft = previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity)
event.preventDefault() if @presenter.canScrollLeftTo(updatedScrollLeft)
@presenter.setScrollLeft(updatedScrollLeft)
else
# Scrolling vertically
@presenter.setMouseWheelScreenRow(@screenRowForNode(event.target))
previousScrollTop = @presenter.getScrollTop()
@presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity))
event.preventDefault() unless previousScrollTop is @presenter.getScrollTop()
updatedScrollTop = previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)
event.preventDefault() if @presenter.canScrollTopTo(updatedScrollTop)
@presenter.setScrollTop(updatedScrollTop)
onScrollViewScroll: =>
if @mounted
@@ -365,6 +373,77 @@ class TextEditorComponent
@scrollViewNode.scrollTop = 0
@scrollViewNode.scrollLeft = 0
onDidChangeScrollTop: (callback) ->
@presenter.onDidChangeScrollTop(callback)
onDidChangeScrollLeft: (callback) ->
@presenter.onDidChangeScrollLeft(callback)
setScrollLeft: (scrollLeft) ->
@presenter.setScrollLeft(scrollLeft)
setScrollRight: (scrollRight) ->
@presenter.setScrollRight(scrollRight)
setScrollTop: (scrollTop) ->
@presenter.setScrollTop(scrollTop)
setScrollBottom: (scrollBottom) ->
@presenter.setScrollBottom(scrollBottom)
getScrollTop: ->
@presenter.getScrollTop()
getScrollLeft: ->
@presenter.getScrollLeft()
getScrollRight: ->
@presenter.getScrollRight()
getScrollBottom: ->
@presenter.getScrollBottom()
getScrollHeight: ->
@presenter.getScrollHeight()
getScrollWidth: ->
@presenter.getScrollWidth()
getVerticalScrollbarWidth: ->
@presenter.getVerticalScrollbarWidth()
getHorizontalScrollbarHeight: ->
@presenter.getHorizontalScrollbarHeight()
getVisibleRowRange: ->
@presenter.getVisibleRowRange()
pixelPositionForScreenPosition: (screenPosition) ->
position = @presenter.pixelPositionForScreenPosition(screenPosition)
position.top += @presenter.getScrollTop()
position.left += @presenter.getScrollLeft()
position
screenPositionForPixelPosition: (pixelPosition) ->
@presenter.screenPositionForPixelPosition(pixelPosition)
pixelRectForScreenRange: (screenRange) ->
rect = @presenter.pixelRectForScreenRange(screenRange)
rect.top += @presenter.getScrollTop()
rect.bottom += @presenter.getScrollTop()
rect.left += @presenter.getScrollLeft()
rect.right += @presenter.getScrollLeft()
rect
pixelRangeForScreenRange: (screenRange, clip=true) ->
{start, end} = Range.fromObject(screenRange)
{start: @pixelPositionForScreenPosition(start, clip), end: @pixelPositionForScreenPosition(end, clip)}
pixelPositionForBufferPosition: (bufferPosition) ->
@pixelPositionForScreenPosition(
@editor.screenPositionForBufferPosition(bufferPosition)
)
onMouseDown: (event) =>
unless event.button is 0 or (event.button is 1 and process.platform is 'linux')
# Only handle mouse down events for left mouse button on all platforms
@@ -546,9 +625,11 @@ class TextEditorComponent
if mouseYDelta?
@presenter.setScrollTop(@presenter.getScrollTop() + yDirection * scaleScrollDelta(mouseYDelta))
@presenter.commitPendingScrollTopPosition()
if mouseXDelta?
@presenter.setScrollLeft(@presenter.getScrollLeft() + xDirection * scaleScrollDelta(mouseXDelta))
@presenter.commitPendingScrollLeftPosition()
scaleScrollDelta = (scrollDelta) ->
Math.pow(scrollDelta / 2, 3) / 280
@@ -774,19 +855,22 @@ class TextEditorComponent
screenPositionForMouseEvent: (event, linesClientRect) ->
pixelPosition = @pixelPositionForMouseEvent(event, linesClientRect)
@editor.screenPositionForPixelPosition(pixelPosition)
@presenter.screenPositionForPixelPosition(pixelPosition)
pixelPositionForMouseEvent: (event, linesClientRect) ->
{clientX, clientY} = event
linesClientRect ?= @linesComponent.getDomNode().getBoundingClientRect()
top = clientY - linesClientRect.top + @presenter.scrollTop
left = clientX - linesClientRect.left + @presenter.scrollLeft
bottom = linesClientRect.top + @presenter.scrollTop + linesClientRect.height - clientY
right = linesClientRect.left + @presenter.scrollLeft + linesClientRect.width - clientX
top = clientY - linesClientRect.top + @presenter.getRealScrollTop()
left = clientX - linesClientRect.left + @presenter.getRealScrollLeft()
bottom = linesClientRect.top + @presenter.getRealScrollTop() + linesClientRect.height - clientY
right = linesClientRect.left + @presenter.getRealScrollLeft() + linesClientRect.width - clientX
{top, left, bottom, right}
getGutterWidth: ->
@presenter.getGutterWidth()
getModel: ->
@editor

View File

@@ -181,7 +181,7 @@ class TextEditorElement extends HTMLElement
#
# Returns an {Object} with two values: `top` and `left`, representing the pixel position.
pixelPositionForBufferPosition: (bufferPosition) ->
@getModel().pixelPositionForBufferPosition(bufferPosition, true)
@component.pixelPositionForBufferPosition(bufferPosition)
# Extended: Converts a screen position to a pixel position.
#
@@ -190,21 +190,21 @@ class TextEditorElement extends HTMLElement
#
# Returns an {Object} with two values: `top` and `left`, representing the pixel positions.
pixelPositionForScreenPosition: (screenPosition) ->
@getModel().pixelPositionForScreenPosition(screenPosition, true)
@component.pixelPositionForScreenPosition(screenPosition)
# Extended: Retrieves the number of the row that is visible and currently at the
# top of the editor.
#
# Returns a {Number}.
getFirstVisibleScreenRow: ->
@getModel().getFirstVisibleScreenRow(true)
@getVisibleRowRange()[0]
# Extended: Retrieves the number of the row that is visible and currently at the
# bottom of the editor.
#
# Returns a {Number}.
getLastVisibleScreenRow: ->
@getModel().getLastVisibleScreenRow(true)
@getVisibleRowRange()[1]
# Extended: call the given `callback` when the editor is attached to the DOM.
#
@@ -218,6 +218,90 @@ class TextEditorElement extends HTMLElement
onDidDetach: (callback) ->
@emitter.on("did-detach", callback)
onDidChangeScrollTop: (callback) ->
@component.onDidChangeScrollTop(callback)
onDidChangeScrollLeft: (callback) ->
@component.onDidChangeScrollLeft(callback)
setScrollLeft: (scrollLeft) ->
@component.setScrollLeft(scrollLeft)
setScrollRight: (scrollRight) ->
@component.setScrollRight(scrollRight)
setScrollTop: (scrollTop) ->
@component.setScrollTop(scrollTop)
setScrollBottom: (scrollBottom) ->
@component.setScrollBottom(scrollBottom)
# Essential: Scrolls the editor to the top
scrollToTop: ->
@setScrollTop(0)
# Essential: Scrolls the editor to the bottom
scrollToBottom: ->
@setScrollBottom(Infinity)
getScrollTop: ->
@component.getScrollTop()
getScrollLeft: ->
@component.getScrollLeft()
getScrollRight: ->
@component.getScrollRight()
getScrollBottom: ->
@component.getScrollBottom()
getScrollHeight: ->
@component.getScrollHeight()
getScrollWidth: ->
@component.getScrollWidth()
getVerticalScrollbarWidth: ->
@component.getVerticalScrollbarWidth()
getHorizontalScrollbarHeight: ->
@component.getHorizontalScrollbarHeight()
getVisibleRowRange: ->
@component.getVisibleRowRange()
intersectsVisibleRowRange: (startRow, endRow) ->
[visibleStart, visibleEnd] = @getVisibleRowRange()
not (endRow <= visibleStart or visibleEnd <= startRow)
selectionIntersectsVisibleRowRange: (selection) ->
{start, end} = selection.getScreenRange()
@intersectsVisibleRowRange(start.row, end.row + 1)
screenPositionForPixelPosition: (pixelPosition) ->
@component.screenPositionForPixelPosition(pixelPosition)
pixelRectForScreenRange: (screenRange) ->
@component.pixelRectForScreenRange(screenRange)
pixelRangeForScreenRange: (screenRange) ->
@component.pixelRangeForScreenRange(screenRange)
setWidth: (width) ->
@style.width = (@component.getGutterWidth() + width) + "px"
@component.measureDimensions()
getWidth: ->
@style.offsetWidth - @component.getGutterWidth()
setHeight: (height) ->
@style.height = height + "px"
@component.measureDimensions()
getHeight: ->
@style.offsetHeight
stopEventPropagation = (commandListeners) ->
newCommandListeners = {}
for commandName, commandListener of commandListeners

View File

@@ -14,7 +14,7 @@ class TextEditorPresenter
minimumReflowInterval: 200
constructor: (params) ->
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @scrollColumn, @scrollRow, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@@ -32,6 +32,7 @@ class TextEditorPresenter
@lineNumberDecorationsByScreenRow = {}
@customGutterDecorationsByGutterNameAndScreenRow = {}
@transferMeasurementsToModel()
@transferMeasurementsFromModel()
@observeModel()
@observeConfig()
@buildState()
@@ -50,14 +51,11 @@ class TextEditorPresenter
@emitter.emit "did-update-state" if @isBatching()
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(@measuredVerticalScrollbarWidth) if @measuredVerticalScrollbarWidth?
@model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight?
transferMeasurementsFromModel: ->
@editorWidthInChars = @model.getEditorWidthInChars()
# Private: Determines whether {TextEditorPresenter} is currently batching changes.
# Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them.
@@ -71,6 +69,7 @@ class TextEditorPresenter
@updateContentDimensions()
@updateScrollbarDimensions()
@updateScrollPosition()
@updateStartRow()
@updateEndRow()
@updateCommonGutterState()
@@ -115,8 +114,6 @@ class TextEditorPresenter
observeModel: ->
@disposables.add @model.onDidChange =>
@updateContentDimensions()
@shouldUpdateHeightState = true
@shouldUpdateVerticalScrollState = true
@shouldUpdateHorizontalScrollState = true
@@ -162,8 +159,7 @@ class TextEditorPresenter
@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))
@disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this))
@observeDecoration(decoration) for decoration in @model.getDecorations()
@observeCursor(cursor) for cursor in @model.getCursors()
@disposables.add @model.onDidAddGutter(@didAddGutter.bind(this))
@@ -235,6 +231,7 @@ class TextEditorPresenter
@shouldUpdateLineNumbersState = true
@updateContentDimensions()
@updateScrollPosition()
@updateScrollbarDimensions()
@updateStartRow()
@updateEndRow()
@@ -690,6 +687,8 @@ class TextEditorPresenter
return unless @height? and @horizontalScrollbarHeight?
clientHeight = @height - @horizontalScrollbarHeight
@model.setHeight(clientHeight, true)
unless @clientHeight is clientHeight
@clientHeight = clientHeight
@updateScrollHeight()
@@ -699,6 +698,8 @@ class TextEditorPresenter
return unless @contentFrameWidth? and @verticalScrollbarWidth?
clientWidth = @contentFrameWidth - @verticalScrollbarWidth
@model.setWidth(clientWidth, true) unless @editorWidthInChars
unless @clientWidth is clientWidth
@clientWidth = clientWidth
@updateScrollWidth()
@@ -708,6 +709,7 @@ class TextEditorPresenter
scrollTop = @constrainScrollTop(@scrollTop)
unless @scrollTop is scrollTop
@scrollTop = scrollTop
@realScrollTop = @scrollTop
@updateStartRow()
@updateEndRow()
@@ -717,6 +719,7 @@ class TextEditorPresenter
updateScrollLeft: ->
@scrollLeft = @constrainScrollLeft(@scrollLeft)
@realScrollLeft = @scrollLeft
constrainScrollLeft: (scrollLeft) ->
return scrollLeft unless scrollLeft? and @scrollWidth? and @clientWidth?
@@ -809,26 +812,28 @@ class TextEditorPresenter
@emitDidUpdateState()
setScrollTop: (scrollTop) ->
scrollTop = @constrainScrollTop(scrollTop)
return unless scrollTop?
unless @scrollTop is scrollTop or Number.isNaN(scrollTop)
@scrollTop = scrollTop
@model.setScrollTop(scrollTop)
@didStartScrolling()
@shouldUpdateVerticalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateCursorsState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateCustomGutterDecorationState = true
@shouldUpdateOverlaysState = true
@pendingScrollLogicalPosition = null
@pendingScrollTop = scrollTop
@emitDidUpdateState()
@shouldUpdateVerticalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateCursorsState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateCustomGutterDecorationState = true
@shouldUpdateOverlaysState = true
@emitDidUpdateState()
getScrollTop: ->
@scrollTop
getRealScrollTop: ->
@realScrollTop ? @scrollTop
didStartScrolling: ->
if @stoppedScrollingTimeoutId?
clearTimeout(@stoppedScrollingTimeoutId)
@@ -848,28 +853,58 @@ class TextEditorPresenter
@emitDidUpdateState()
setScrollLeft: (scrollLeft) ->
scrollLeft = @constrainScrollLeft(scrollLeft)
unless @scrollLeft is scrollLeft or Number.isNaN(scrollLeft)
oldScrollLeft = @scrollLeft
@scrollLeft = scrollLeft
@model.setScrollLeft(scrollLeft)
@shouldUpdateHorizontalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateCursorsState = true
@shouldUpdateOverlaysState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
return unless scrollLeft?
@emitDidUpdateState()
@pendingScrollLogicalPosition = null
@pendingScrollLeft = scrollLeft
@shouldUpdateHorizontalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateCursorsState = true
@shouldUpdateOverlaysState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@emitDidUpdateState()
getScrollLeft: ->
@scrollLeft
getRealScrollLeft: ->
@realScrollLeft ? @scrollLeft
getClientHeight: ->
if @clientHeight
@clientHeight
else
@explicitHeight - @horizontalScrollbarHeight
getClientWidth: ->
if @clientWidth
@clientWidth
else
@contentFrameWidth - @verticalScrollbarWidth
getScrollBottom: -> @getScrollTop() + @getClientHeight()
setScrollBottom: (scrollBottom) ->
@setScrollTop(scrollBottom - @getClientHeight())
@getScrollBottom()
getScrollRight: -> @getScrollLeft() + @getClientWidth()
setScrollRight: (scrollRight) ->
@setScrollLeft(scrollRight - @getClientWidth())
@getScrollRight()
getScrollHeight: ->
@scrollHeight
getScrollWidth: ->
@scrollWidth
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight
oldHorizontalScrollbarHeight = @measuredHorizontalScrollbarHeight
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
@model.setHorizontalScrollbarHeight(horizontalScrollbarHeight)
@shouldUpdateScrollbarsState = true
@shouldUpdateVerticalScrollState = true
@shouldUpdateHorizontalScrollState = true
@@ -881,7 +916,6 @@ class TextEditorPresenter
unless @measuredVerticalScrollbarWidth is verticalScrollbarWidth
oldVerticalScrollbarWidth = @measuredVerticalScrollbarWidth
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@model.setVerticalScrollbarWidth(verticalScrollbarWidth)
@shouldUpdateScrollbarsState = true
@shouldUpdateVerticalScrollState = true
@shouldUpdateHorizontalScrollState = true
@@ -899,7 +933,6 @@ class TextEditorPresenter
setExplicitHeight: (explicitHeight) ->
unless @explicitHeight is explicitHeight
@explicitHeight = explicitHeight
@model.setHeight(explicitHeight)
@updateHeight()
@shouldUpdateVerticalScrollState = true
@shouldUpdateScrollbarsState = true
@@ -921,10 +954,10 @@ class TextEditorPresenter
@updateEndRow()
setContentFrameWidth: (contentFrameWidth) ->
unless @contentFrameWidth is contentFrameWidth
if @contentFrameWidth isnt contentFrameWidth or @editorWidthInChars?
oldContentFrameWidth = @contentFrameWidth
@contentFrameWidth = contentFrameWidth
@model.setWidth(contentFrameWidth)
@editorWidthInChars = null
@updateScrollbarDimensions()
@updateClientWidth()
@shouldUpdateVerticalScrollState = true
@@ -982,6 +1015,9 @@ class TextEditorPresenter
@gutterWidth = gutterWidth
@updateOverlaysState()
getGutterWidth: ->
@gutterWidth
setLineHeight: (lineHeight) ->
unless @lineHeight is lineHeight
@lineHeight = lineHeight
@@ -1438,3 +1474,179 @@ class TextEditorPresenter
@startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay())
@startBlinkingCursorsAfterDelay()
@emitDidUpdateState()
requestAutoscroll: (position) ->
@pendingScrollLogicalPosition = position
@pendingScrollTop = null
@pendingScrollLeft = null
@shouldUpdateCursorsState = true
@shouldUpdateCustomGutterDecorationState = true
@shouldUpdateDecorations = true
@shouldUpdateHiddenInputState = true
@shouldUpdateHorizontalScrollState = true
@shouldUpdateLinesState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateOverlaysState = true
@shouldUpdateScrollPosition = true
@shouldUpdateVerticalScrollState = true
@emitDidUpdateState()
getVerticalScrollMarginInPixels: ->
@model.getVerticalScrollMargin() * @lineHeight
getHorizontalScrollMarginInPixels: ->
@model.getHorizontalScrollMargin() * @baseCharacterWidth
getVerticalScrollbarWidth: ->
@verticalScrollbarWidth
getHorizontalScrollbarHeight: ->
@horizontalScrollbarHeight
commitPendingLogicalScrollPosition: ->
return unless @pendingScrollLogicalPosition?
{screenRange, options} = @pendingScrollLogicalPosition
verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels()
{top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start))
{top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end))
bottom = endTop + endHeight
right = endLeft
top += @scrollTop
bottom += @scrollTop
left += @scrollLeft
right += @scrollLeft
if options?.center
desiredScrollCenter = (top + bottom) / 2
unless @getScrollTop() < desiredScrollCenter < @getScrollBottom()
desiredScrollTop = desiredScrollCenter - @getClientHeight() / 2
desiredScrollBottom = desiredScrollCenter + @getClientHeight() / 2
else
desiredScrollTop = top - verticalScrollMarginInPixels
desiredScrollBottom = bottom + verticalScrollMarginInPixels
desiredScrollLeft = left - horizontalScrollMarginInPixels
desiredScrollRight = right + horizontalScrollMarginInPixels
if options?.reversed ? true
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
else
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
@pendingScrollLogicalPosition = null
commitPendingScrollLeftPosition: ->
return unless @pendingScrollLeft?
scrollLeft = @constrainScrollLeft(@pendingScrollLeft)
if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft)
@realScrollLeft = scrollLeft
@scrollLeft = Math.round(scrollLeft)
@scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth)
@model.setScrollColumn(@scrollColumn)
@emitter.emit 'did-change-scroll-left', @scrollLeft
@pendingScrollLeft = null
commitPendingScrollTopPosition: ->
return unless @pendingScrollTop?
scrollTop = @constrainScrollTop(@pendingScrollTop)
if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop)
@realScrollTop = scrollTop
@scrollTop = Math.round(scrollTop)
@scrollRow = Math.round(@scrollTop / @lineHeight)
@model.setScrollRow(@scrollRow)
@didStartScrolling()
@emitter.emit 'did-change-scroll-top', @scrollTop
@pendingScrollTop = null
restoreScrollPosition: ->
return if @hasRestoredScrollPosition or not @hasPixelPositionRequirements()
@setScrollTop(@scrollRow * @lineHeight) if @scrollRow?
@setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn?
@hasRestoredScrollPosition = true
updateScrollPosition: ->
@restoreScrollPosition()
@commitPendingLogicalScrollPosition()
@commitPendingScrollLeftPosition()
@commitPendingScrollTopPosition()
canScrollLeftTo: (scrollLeft) ->
@scrollLeft isnt @constrainScrollLeft(scrollLeft)
canScrollTopTo: (scrollTop) ->
@scrollTop isnt @constrainScrollTop(scrollTop)
onDidChangeScrollTop: (callback) ->
@emitter.on 'did-change-scroll-top', callback
onDidChangeScrollLeft: (callback) ->
@emitter.on 'did-change-scroll-left', callback
getVisibleRowRange: ->
[@startRow, @endRow]
screenPositionForPixelPosition: (pixelPosition) ->
targetTop = pixelPosition.top
targetLeft = pixelPosition.left
defaultCharWidth = @baseCharacterWidth
row = Math.floor(targetTop / @lineHeight)
targetLeft = 0 if row < 0
targetLeft = Infinity if row > @model.getLastScreenRow()
row = Math.min(row, @model.getLastScreenRow())
row = Math.max(0, row)
left = 0
column = 0
iterator = @model.tokenizedLineForScreenRow(row).getTokenIterator()
while iterator.next()
charWidths = @getScopedCharacterWidths(iterator.getScopes())
value = iterator.getText()
valueIndex = 0
while valueIndex < value.length
if iterator.isPairedCharacter()
char = value
charLength = 2
valueIndex += 2
else
char = value[valueIndex]
charLength = 1
valueIndex++
charWidth = charWidths[char] ? defaultCharWidth
break if targetLeft <= left + (charWidth / 2)
left += charWidth
column += charLength
new Point(row, column)

View File

@@ -80,7 +80,7 @@ class TextEditor extends Model
state.registerEditor = true
new this(state)
constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) ->
constructor: ({@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) ->
super
@emitter = new Emitter
@@ -109,12 +109,6 @@ class TextEditor extends Model
@setEncoding(atom.config.get('core.fileEncoding', scope: @getRootScopeDescriptor()))
@disposables.add @displayBuffer.onDidChangeScrollTop (scrollTop) =>
@emitter.emit 'did-change-scroll-top', scrollTop
@disposables.add @displayBuffer.onDidChangeScrollLeft (scrollLeft) =>
@emitter.emit 'did-change-scroll-left', scrollLeft
@gutterContainer = new GutterContainer(this)
@lineNumberGutter = @gutterContainer.addGutter
name: 'line-number'
@@ -127,8 +121,8 @@ class TextEditor extends Model
deserializer: 'TextEditor'
id: @id
softTabs: @softTabs
scrollTop: @scrollTop
scrollLeft: @scrollLeft
scrollRow: @getScrollRow()
scrollColumn: @getScrollColumn()
displayBuffer: @displayBuffer.serialize()
subscribeToBuffer: ->
@@ -433,10 +427,17 @@ class TextEditor extends Model
@displayBuffer.onDidChangeCharacterWidths(callback)
onDidChangeScrollTop: (callback) ->
@emitter.on 'did-change-scroll-top', callback
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.")
atom.views.getView(this).onDidChangeScrollTop(callback)
onDidChangeScrollLeft: (callback) ->
@emitter.on 'did-change-scroll-left', callback
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollLeft instead.")
atom.views.getView(this).onDidChangeScrollLeft(callback)
onDidRequestAutoscroll: (callback) ->
@displayBuffer.onDidRequestAutoscroll(callback)
# TODO Remove once the tabs package no longer uses .on subscriptions
onDidChangeIcon: (callback) ->
@@ -525,6 +526,10 @@ class TextEditor extends Model
setEditorWidthInChars: (editorWidthInChars) ->
@displayBuffer.setEditorWidthInChars(editorWidthInChars)
# Returns the editor width in characters.
getEditorWidthInChars: ->
@displayBuffer.getEditorWidthInChars()
###
Section: File Details
###
@@ -2852,25 +2857,27 @@ class TextEditor extends Model
scrollToScreenPosition: (screenPosition, options) ->
@displayBuffer.scrollToScreenPosition(screenPosition, options)
# Essential: Scrolls the editor to the top
scrollToTop: ->
@setScrollTop(0)
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
atom.views.getView(this).scrollToTop()
# Essential: Scrolls the editor to the bottom
scrollToBottom: ->
@setScrollBottom(Infinity)
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
atom.views.getView(this).scrollToBottom()
scrollToScreenRange: (screenRange, options) -> @displayBuffer.scrollToScreenRange(screenRange, options)
horizontallyScrollable: -> @displayBuffer.horizontallyScrollable()
getHorizontalScrollbarHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.")
verticallyScrollable: -> @displayBuffer.verticallyScrollable()
atom.views.getView(this).getHorizontalScrollbarHeight()
getHorizontalScrollbarHeight: -> @displayBuffer.getHorizontalScrollbarHeight()
setHorizontalScrollbarHeight: (height) -> @displayBuffer.setHorizontalScrollbarHeight(height)
getVerticalScrollbarWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVerticalScrollbarWidth instead.")
getVerticalScrollbarWidth: -> @displayBuffer.getVerticalScrollbarWidth()
setVerticalScrollbarWidth: (width) -> @displayBuffer.setVerticalScrollbarWidth(width)
atom.views.getView(this).getVerticalScrollbarWidth()
pageUp: ->
@moveUp(@getRowsPerPage())
@@ -2933,25 +2940,21 @@ class TextEditor extends Model
@placeholderText = placeholderText
@emitter.emit 'did-change-placeholder-text', @placeholderText
getFirstVisibleScreenRow: (suppressDeprecation) ->
unless suppressDeprecation
deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.")
@getVisibleRowRange()[0]
getFirstVisibleScreenRow: ->
deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.")
atom.views.getView(this).getVisibleRowRange()[0]
getLastVisibleScreenRow: (suppressDeprecation) ->
unless suppressDeprecation
Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.")
@getVisibleRowRange()[1]
getLastVisibleScreenRow: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.")
atom.views.getView(this).getVisibleRowRange()[1]
pixelPositionForBufferPosition: (bufferPosition, suppressDeprecation) ->
unless suppressDeprecation
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead")
@displayBuffer.pixelPositionForBufferPosition(bufferPosition)
pixelPositionForBufferPosition: (bufferPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead")
atom.views.getView(this).pixelPositionForBufferPosition(bufferPosition)
pixelPositionForScreenPosition: (screenPosition, suppressDeprecation) ->
unless suppressDeprecation
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead")
@displayBuffer.pixelPositionForScreenPosition(screenPosition)
pixelPositionForScreenPosition: (screenPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead")
atom.views.getView(this).pixelPositionForScreenPosition(screenPosition)
getSelectionMarkerAttributes: ->
{type: 'selection', editorId: @id, invalidate: 'never', maintainHistory: true}
@@ -2977,38 +2980,110 @@ class TextEditor extends Model
getDefaultCharWidth: -> @displayBuffer.getDefaultCharWidth()
setDefaultCharWidth: (defaultCharWidth) -> @displayBuffer.setDefaultCharWidth(defaultCharWidth)
setHeight: (height) -> @displayBuffer.setHeight(height)
getHeight: -> @displayBuffer.getHeight()
setHeight: (height, reentrant=false) ->
if reentrant
@displayBuffer.setHeight(height)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setHeight instead.")
atom.views.getView(this).setHeight(height)
getHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHeight instead.")
@displayBuffer.getHeight()
getClientHeight: -> @displayBuffer.getClientHeight()
setWidth: (width) -> @displayBuffer.setWidth(width)
getWidth: -> @displayBuffer.getWidth()
setWidth: (width, reentrant=false) ->
if reentrant
@displayBuffer.setWidth(width)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setWidth instead.")
atom.views.getView(this).setWidth(width)
getScrollTop: -> @displayBuffer.getScrollTop()
setScrollTop: (scrollTop) -> @displayBuffer.setScrollTop(scrollTop)
getWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.")
@displayBuffer.getWidth()
getScrollBottom: -> @displayBuffer.getScrollBottom()
setScrollBottom: (scrollBottom) -> @displayBuffer.setScrollBottom(scrollBottom)
getScrollRow: -> @scrollRow
setScrollRow: (@scrollRow) ->
getScrollLeft: -> @displayBuffer.getScrollLeft()
setScrollLeft: (scrollLeft) -> @displayBuffer.setScrollLeft(scrollLeft)
getScrollColumn: -> @scrollColumn
setScrollColumn: (@scrollColumn) ->
getScrollRight: -> @displayBuffer.getScrollRight()
setScrollRight: (scrollRight) -> @displayBuffer.setScrollRight(scrollRight)
getScrollTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.")
getScrollHeight: -> @displayBuffer.getScrollHeight()
getScrollWidth: -> @displayBuffer.getScrollWidth()
atom.views.getView(this).getScrollTop()
getVisibleRowRange: -> @displayBuffer.getVisibleRowRange()
setScrollTop: (scrollTop) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollTop instead.")
intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow)
atom.views.getView(this).setScrollTop(scrollTop)
selectionIntersectsVisibleRowRange: (selection) -> @displayBuffer.selectionIntersectsVisibleRowRange(selection)
getScrollBottom: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollBottom instead.")
screenPositionForPixelPosition: (pixelPosition) -> @displayBuffer.screenPositionForPixelPosition(pixelPosition)
atom.views.getView(this).getScrollBottom()
pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange)
setScrollBottom: (scrollBottom) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollBottom instead.")
atom.views.getView(this).setScrollBottom(scrollBottom)
getScrollLeft: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollLeft instead.")
atom.views.getView(this).getScrollLeft()
setScrollLeft: (scrollLeft) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollLeft instead.")
atom.views.getView(this).setScrollLeft(scrollLeft)
getScrollRight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollRight instead.")
atom.views.getView(this).getScrollRight()
setScrollRight: (scrollRight) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollRight instead.")
atom.views.getView(this).setScrollRight(scrollRight)
getScrollHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollHeight instead.")
atom.views.getView(this).getScrollHeight()
getScrollWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollWidth instead.")
atom.views.getView(this).getScrollWidth()
getVisibleRowRange: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVisibleRowRange instead.")
atom.views.getView(this).getVisibleRowRange()
intersectsVisibleRowRange: (startRow, endRow) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.")
atom.views.getView(this).intersectsVisibleRowRange(startRow, endRow)
selectionIntersectsVisibleRowRange: (selection) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::selectionIntersectsVisibleRowRange instead.")
atom.views.getView(this).selectionIntersectsVisibleRowRange(selection)
screenPositionForPixelPosition: (pixelPosition) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::screenPositionForPixelPosition instead.")
atom.views.getView(this).screenPositionForPixelPosition(pixelPosition)
pixelRectForScreenRange: (screenRange) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::pixelRectForScreenRange instead.")
atom.views.getView(this).pixelRectForScreenRange(screenRange)
###
Section: Utility