Introduce synthetic scrolling

We previously thought scroll events had changed somehow to become
synchronous, but were wrong. This introduces synthetic scrolling where
we use GPU translation of the contents of the gutter and scroll
containers to simulate scrolling and explicitly capture mousewheel
events.

Still need to add dummy scrollbars and deal with their footprint in
clientHeight and clientWidth.
This commit is contained in:
Nathan Sobo 2017-03-18 22:53:26 -07:00 committed by Antonio Scandurra
parent 0e747a400d
commit 5a47f179e3
3 changed files with 351 additions and 377 deletions

View File

@ -37,7 +37,7 @@ describe('TextEditorComponent', () => {
expect(element.querySelectorAll('.line-number').length).toBe(9)
expect(element.querySelectorAll('.line').length).toBe(9)
component.refs.scroller.scrollTop = 5 * component.measurements.lineHeight
component.setScrollTop(5 * component.getLineHeight())
await component.getNextUpdatePromise()
// After scrolling down beyond > 3 rows, the order of line numbers and lines
@ -58,7 +58,7 @@ describe('TextEditorComponent', () => {
editor.lineTextForScreenRow(8)
])
component.refs.scroller.scrollTop = 2.5 * component.measurements.lineHeight
component.setScrollTop(2.5 * component.getLineHeight())
await component.getNextUpdatePromise()
expect(Array.from(element.querySelectorAll('.line-number')).map(element => element.textContent.trim())).toEqual([
'1', '2', '3', '4', '5', '6', '7', '8', '9'
@ -88,25 +88,24 @@ describe('TextEditorComponent', () => {
it('honors the scrollPastEnd option by adding empty space equivalent to the clientHeight to the end of the content area', async () => {
const {component, element, editor} = buildComponent({autoHeight: false, autoWidth: false})
const {scroller} = component.refs
const {scrollContainer} = component.refs
await editor.update({scrollPastEnd: true})
await setEditorHeightInLines(component, 6)
// scroll to end
scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight
component.setScrollTop(scrollContainer.scrollHeight - scrollContainer.clientHeight)
await component.getNextUpdatePromise()
expect(component.getFirstVisibleRow()).toBe(editor.getScreenLineCount() - 3)
editor.update({scrollPastEnd: false})
await component.getNextUpdatePromise() // wait for scrollable content resize
await component.getNextUpdatePromise() // wait for async scroll event due to scrollbar shrinking
expect(component.getFirstVisibleRow()).toBe(editor.getScreenLineCount() - 6)
// Always allows at least 3 lines worth of overscroll if the editor is short
await setEditorHeightInLines(component, 2)
await editor.update({scrollPastEnd: true})
scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight
component.setScrollTop(scrollContainer.scrollHeight - scrollContainer.clientHeight)
await component.getNextUpdatePromise()
expect(component.getFirstVisibleRow()).toBe(editor.getScreenLineCount() + 1)
})
@ -125,18 +124,9 @@ describe('TextEditorComponent', () => {
expect(gutterElement.firstChild.style.contain).toBe('strict')
})
it('translates the gutter so it is always visible when scrolling to the right', async () => {
const {component, element, editor} = buildComponent({width: 100})
expect(component.refs.gutterContainer.style.transform).toBe('translateX(0px)')
component.refs.scroller.scrollLeft = 100
await component.getNextUpdatePromise()
expect(component.refs.gutterContainer.style.transform).toBe('translateX(100px)')
})
it('renders cursors within the visible row range', async () => {
const {component, element, editor} = buildComponent({height: 40, rowsPerTile: 2})
component.refs.scroller.scrollTop = 100
component.setScrollTop(100)
await component.getNextUpdatePromise()
expect(component.getRenderedStartRow()).toBe(4)
@ -180,8 +170,8 @@ describe('TextEditorComponent', () => {
it('places the hidden input element at the location of the last cursor if it is visible', async () => {
const {component, element, editor} = buildComponent({height: 60, width: 120, rowsPerTile: 2})
const {hiddenInput} = component.refs
component.refs.scroller.scrollTop = 100
component.refs.scroller.scrollLeft = 40
component.setScrollTop(100)
component.setScrollLeft(40)
await component.getNextUpdatePromise()
expect(component.getRenderedStartRow()).toBe(4)
@ -205,6 +195,7 @@ describe('TextEditorComponent', () => {
jasmine.attachToDOM(element)
expect(getBaseCharacterWidth(component)).toBe(55)
console.log('running expectation');
expect(lineNodeForScreenRow(component, 3).textContent).toBe(
' var pivot = items.shift(), current, left = [], '
)
@ -220,8 +211,8 @@ describe('TextEditorComponent', () => {
' = [], right = [];'
)
const {scroller} = component.refs
expect(scroller.clientWidth).toBe(scroller.scrollWidth)
const {scrollContainer} = component.refs
expect(scrollContainer.clientWidth).toBe(scrollContainer.scrollWidth)
})
it('decorates the line numbers of folded lines', async () => {
@ -231,29 +222,30 @@ describe('TextEditorComponent', () => {
expect(lineNumberNodeForScreenRow(component, 1).classList.contains('folded')).toBe(true)
})
it('makes lines at least as wide as the scroller', async () => {
it('makes lines at least as wide as the scrollContainer', async () => {
const {component, element, editor} = buildComponent()
const {scroller, gutterContainer} = component.refs
const {scrollContainer, gutterContainer} = component.refs
editor.setText('a')
await component.getNextUpdatePromise()
expect(element.querySelector('.line').offsetWidth).toBe(scroller.offsetWidth - gutterContainer.offsetWidth)
expect(element.querySelector('.line').offsetWidth).toBe(scrollContainer.offsetWidth)
})
it('resizes based on the content when the autoHeight and/or autoWidth options are true', async () => {
const {component, element, editor} = buildComponent({autoHeight: true, autoWidth: true})
const {gutterContainer, scrollContainer} = component.refs
const initialWidth = element.offsetWidth
const initialHeight = element.offsetHeight
expect(initialWidth).toBe(component.refs.scroller.scrollWidth)
expect(initialHeight).toBe(component.refs.scroller.scrollHeight)
expect(initialWidth).toBe(gutterContainer.offsetWidth + scrollContainer.scrollWidth)
expect(initialHeight).toBe(scrollContainer.scrollHeight)
editor.setCursorScreenPosition([6, Infinity])
editor.insertText('x'.repeat(50))
await component.getNextUpdatePromise()
expect(element.offsetWidth).toBe(component.refs.scroller.scrollWidth)
expect(element.offsetWidth).toBe(gutterContainer.offsetWidth + scrollContainer.scrollWidth)
expect(element.offsetWidth).toBeGreaterThan(initialWidth)
editor.insertText('\n'.repeat(5))
await component.getNextUpdatePromise()
expect(element.offsetHeight).toBe(component.refs.scroller.scrollHeight)
expect(element.offsetHeight).toBe(scrollContainer.scrollHeight)
expect(element.offsetHeight).toBeGreaterThan(initialHeight)
})
@ -369,32 +361,29 @@ describe('TextEditorComponent', () => {
describe('autoscroll on cursor movement', () => {
it('automatically scrolls vertically when the requested range is within the vertical scroll margin of the top or bottom', async () => {
const {component, element, editor} = buildComponent({height: 120})
const {scroller} = component.refs
const {component, editor} = buildComponent({height: 120})
expect(component.getLastVisibleRow()).toBe(8)
editor.scrollToScreenRange([[4, 0], [6, 0]])
await component.getNextUpdatePromise()
let scrollBottom = scroller.scrollTop + scroller.clientHeight
expect(scrollBottom).toBe((6 + 1 + editor.verticalScrollMargin) * component.measurements.lineHeight)
expect(component.getScrollBottom()).toBe((6 + 1 + editor.verticalScrollMargin) * component.getLineHeight())
editor.scrollToScreenPosition([8, 0])
await component.getNextUpdatePromise()
scrollBottom = scroller.scrollTop + scroller.clientHeight
expect(scrollBottom).toBe((8 + 1 + editor.verticalScrollMargin) * component.measurements.lineHeight)
expect(component.getScrollBottom()).toBe((8 + 1 + editor.verticalScrollMargin) * component.measurements.lineHeight)
editor.scrollToScreenPosition([3, 0])
await component.getNextUpdatePromise()
expect(scroller.scrollTop).toBe((3 - editor.verticalScrollMargin) * component.measurements.lineHeight)
expect(component.getScrollTop()).toBe((3 - editor.verticalScrollMargin) * component.measurements.lineHeight)
editor.scrollToScreenPosition([2, 0])
await component.getNextUpdatePromise()
expect(scroller.scrollTop).toBe(0)
expect(component.getScrollTop()).toBe(0)
})
it('does not vertically autoscroll by more than half of the visible lines if the editor is shorter than twice the scroll margin', async () => {
const {component, element, editor} = buildComponent({autoHeight: false})
const {scroller} = component.refs
const {scrollContainer} = component.refs
element.style.height = 5.5 * component.measurements.lineHeight + 'px'
await component.getNextUpdatePromise()
expect(component.getLastVisibleRow()).toBe(6)
@ -402,26 +391,24 @@ describe('TextEditorComponent', () => {
editor.scrollToScreenPosition([6, 0])
await component.getNextUpdatePromise()
let scrollBottom = scroller.scrollTop + scroller.clientHeight
expect(scrollBottom).toBe((6 + 1 + scrollMarginInLines) * component.measurements.lineHeight)
expect(component.getScrollBottom()).toBe((6 + 1 + scrollMarginInLines) * component.measurements.lineHeight)
editor.scrollToScreenPosition([6, 4])
await component.getNextUpdatePromise()
scrollBottom = scroller.scrollTop + scroller.clientHeight
expect(scrollBottom).toBe((6 + 1 + scrollMarginInLines) * component.measurements.lineHeight)
expect(component.getScrollBottom()).toBe((6 + 1 + scrollMarginInLines) * component.measurements.lineHeight)
editor.scrollToScreenRange([[4, 4], [6, 4]])
await component.getNextUpdatePromise()
expect(scroller.scrollTop).toBe((4 - scrollMarginInLines) * component.measurements.lineHeight)
expect(component.getScrollTop()).toBe((4 - scrollMarginInLines) * component.measurements.lineHeight)
editor.scrollToScreenRange([[4, 4], [6, 4]], {reversed: false})
await component.getNextUpdatePromise()
expect(scrollBottom).toBe((6 + 1 + scrollMarginInLines) * component.measurements.lineHeight)
expect(component.getScrollBottom()).toBe((6 + 1 + scrollMarginInLines) * component.measurements.lineHeight)
})
it('automatically scrolls horizontally when the requested range is within the horizontal scroll margin of the right edge of the gutter or right edge of the screen', async () => {
it('automatically scrolls horizontally when the requested range is within the horizontal scroll margin of the right edge of the gutter or right edge of the scroll container', async () => {
const {component, element, editor} = buildComponent()
const {scroller} = component.refs
const {scrollContainer} = component.refs
element.style.width =
component.getGutterContainerWidth() +
3 * editor.horizontalScrollMargin * component.measurements.baseCharacterWidth + 'px'
@ -429,32 +416,30 @@ describe('TextEditorComponent', () => {
editor.scrollToScreenRange([[1, 12], [2, 28]])
await component.getNextUpdatePromise()
let expectedScrollLeft = Math.floor(
let expectedScrollLeft = Math.round(
clientLeftForCharacter(component, 1, 12) -
lineNodeForScreenRow(component, 1).getBoundingClientRect().left -
(editor.horizontalScrollMargin * component.measurements.baseCharacterWidth)
)
expect(scroller.scrollLeft).toBe(expectedScrollLeft)
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
editor.scrollToScreenRange([[1, 12], [2, 28]], {reversed: false})
await component.getNextUpdatePromise()
expectedScrollLeft = Math.floor(
expectedScrollLeft = Math.round(
component.getGutterContainerWidth() +
clientLeftForCharacter(component, 2, 28) -
lineNodeForScreenRow(component, 2).getBoundingClientRect().left +
(editor.horizontalScrollMargin * component.measurements.baseCharacterWidth) -
scroller.clientWidth
scrollContainer.clientWidth
)
expect(scroller.scrollLeft).toBe(expectedScrollLeft)
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
})
it('does not horizontally autoscroll by more than half of the visible "base-width" characters if the editor is narrower than twice the scroll margin', async () => {
const {component, element, editor} = buildComponent({autoHeight: false})
const {scroller, gutterContainer} = component.refs
const {component, editor} = buildComponent({autoHeight: false})
await setEditorWidthInCharacters(component, 1.5 * editor.horizontalScrollMargin)
const contentWidth = scroller.clientWidth - gutterContainer.offsetWidth
const contentWidthInCharacters = Math.floor(contentWidth / component.measurements.baseCharacterWidth)
const contentWidthInCharacters = Math.floor(component.getScrollContainerClientWidth() / component.getBaseCharacterWidth())
expect(contentWidthInCharacters).toBe(9)
editor.scrollToScreenRange([[6, 10], [6, 15]])
@ -462,27 +447,26 @@ describe('TextEditorComponent', () => {
let expectedScrollLeft = Math.floor(
clientLeftForCharacter(component, 6, 10) -
lineNodeForScreenRow(component, 1).getBoundingClientRect().left -
(4 * component.measurements.baseCharacterWidth)
(4 * component.getBaseCharacterWidth())
)
expect(scroller.scrollLeft).toBe(expectedScrollLeft)
expect(component.getScrollLeft()).toBe(expectedScrollLeft)
})
it('correctly autoscrolls after inserting a line that exceeds the current content width', async () => {
const {component, element, editor} = buildComponent()
const {scroller} = component.refs
element.style.width = component.getGutterContainerWidth() + component.measurements.longestLineWidth + 'px'
element.style.width = component.getGutterContainerWidth() + component.getContentWidth() + 'px'
await component.getNextUpdatePromise()
editor.setCursorScreenPosition([0, Infinity])
editor.insertText('x'.repeat(100))
await component.getNextUpdatePromise()
expect(scroller.scrollLeft).toBe(component.getScrollWidth() - scroller.clientWidth)
expect(component.getScrollLeft()).toBe(component.getScrollWidth() - component.getScrollContainerClientWidth())
})
it('accounts for the presence of horizontal scrollbars that appear during the same frame as the autoscroll', async () => {
const {component, element, editor} = buildComponent()
const {scroller} = component.refs
const {scrollContainer} = component.refs
element.style.height = component.getScrollHeight() + 'px'
element.style.width = component.getScrollWidth() + 'px'
await component.getNextUpdatePromise()
@ -491,8 +475,8 @@ describe('TextEditorComponent', () => {
editor.insertText('\n\n' + 'x'.repeat(100))
await component.getNextUpdatePromise()
expect(scroller.scrollTop).toBe(component.getScrollHeight() - scroller.clientHeight)
expect(scroller.scrollLeft).toBe(component.getScrollWidth() - scroller.clientWidth)
expect(component.getScrollTop()).toBe(component.getScrollHeight() - component.getScrollContainerClientHeight())
expect(component.getScrollLeft()).toBe(component.getScrollWidth() - component.getScrollContainerClientWidth())
})
})
@ -735,7 +719,7 @@ describe('TextEditorComponent', () => {
)
// Don't flash on next update if another flash wasn't requested
component.refs.scroller.scrollTop = 100
component.setScrollTop(100)
await component.getNextUpdatePromise()
expect(highlights[0].classList.contains('b')).toBe(false)
expect(highlights[1].classList.contains('b')).toBe(false)
@ -1156,29 +1140,29 @@ describe('TextEditorComponent', () => {
expect(editor.getCursorScreenPosition()).toEqual([0, 0])
})
it('autoscrolls the content when dragging near the edge of the screen', async () => {
const {component, editor} = buildComponent({width: 200, height: 200})
const {scroller} = component.refs
it('autoscrolls the content when dragging near the edge of the scroll container', async () => {
const {component, element, editor} = buildComponent({width: 200, height: 200})
spyOn(component, 'handleMouseDragUntilMouseUp')
let previousScrollTop = 0
let previousScrollLeft = 0
function assertScrolledDownAndRight () {
expect(scroller.scrollTop).toBeGreaterThan(previousScrollTop)
previousScrollTop = scroller.scrollTop
expect(scroller.scrollLeft).toBeGreaterThan(previousScrollLeft)
previousScrollLeft = scroller.scrollLeft
expect(component.getScrollTop()).toBeGreaterThan(previousScrollTop)
previousScrollTop = component.getScrollTop()
expect(component.getScrollLeft()).toBeGreaterThan(previousScrollLeft)
previousScrollLeft = component.getScrollLeft()
}
function assertScrolledUpAndLeft () {
expect(scroller.scrollTop).toBeLessThan(previousScrollTop)
previousScrollTop = scroller.scrollTop
expect(scroller.scrollLeft).toBeLessThan(previousScrollLeft)
previousScrollLeft = scroller.scrollLeft
expect(component.getScrollTop()).toBeLessThan(previousScrollTop)
previousScrollTop = component.getScrollTop()
expect(component.getScrollLeft()).toBeLessThan(previousScrollLeft)
previousScrollLeft = component.getScrollLeft()
}
component.didMouseDownOnContent({detail: 1, button: 0, clientX: 100, clientY: 100})
const {didDrag, didStopDragging} = component.handleMouseDragUntilMouseUp.argsForCall[0][0]
didDrag({clientX: 199, clientY: 199})
assertScrolledDownAndRight()
didDrag({clientX: 199, clientY: 199})
@ -1192,27 +1176,24 @@ describe('TextEditorComponent', () => {
didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1})
assertScrolledUpAndLeft()
// Don't artificially update scroll measurements beyond the minimum or
// maximum possible scroll positions
expect(scroller.scrollTop).toBe(0)
expect(scroller.scrollLeft).toBe(0)
// Don't artificially update scroll position beyond possible values
expect(component.getScrollTop()).toBe(0)
expect(component.getScrollLeft()).toBe(0)
didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1})
expect(component.measurements.scrollTop).toBe(0)
expect(scroller.scrollTop).toBe(0)
expect(component.measurements.scrollLeft).toBe(0)
expect(scroller.scrollLeft).toBe(0)
expect(component.getScrollTop()).toBe(0)
expect(component.getScrollLeft()).toBe(0)
const maxScrollTop = scroller.scrollHeight - scroller.clientHeight
const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth
scroller.scrollTop = maxScrollTop
scroller.scrollLeft = maxScrollLeft
const maxScrollTop = component.getMaxScrollTop()
const maxScrollLeft = component.getMaxScrollLeft()
component.setScrollTop(maxScrollTop)
component.setScrollLeft(maxScrollLeft)
await component.getNextUpdatePromise()
didDrag({clientX: 199, clientY: 199})
didDrag({clientX: 199, clientY: 199})
didDrag({clientX: 199, clientY: 199})
expect(component.measurements.scrollTop).toBe(maxScrollTop)
expect(component.measurements.scrollLeft).toBe(maxScrollLeft)
expect(component.getScrollTop()).toBe(maxScrollTop)
expect(component.getScrollLeft()).toBe(maxScrollLeft)
})
})
@ -1387,25 +1368,25 @@ describe('TextEditorComponent', () => {
expect(editor.isFoldedAtScreenRow(1)).toBe(false)
})
it('autoscrolls the content when dragging near the edge of the screen', async () => {
it('autoscrolls when dragging near the top or bottom of the gutter', async () => {
const {component, editor} = buildComponent({width: 200, height: 200})
const {scroller} = component.refs
const {scrollContainer} = component.refs
spyOn(component, 'handleMouseDragUntilMouseUp')
let previousScrollTop = 0
let previousScrollLeft = 0
function assertScrolledDown () {
expect(scroller.scrollTop).toBeGreaterThan(previousScrollTop)
previousScrollTop = scroller.scrollTop
expect(scroller.scrollLeft).toBe(previousScrollLeft)
previousScrollLeft = scroller.scrollLeft
expect(component.getScrollTop()).toBeGreaterThan(previousScrollTop)
previousScrollTop = component.getScrollTop()
expect(component.getScrollLeft()).toBe(previousScrollLeft)
previousScrollLeft = component.getScrollLeft()
}
function assertScrolledUp () {
expect(scroller.scrollTop).toBeLessThan(previousScrollTop)
previousScrollTop = scroller.scrollTop
expect(scroller.scrollLeft).toBe(previousScrollLeft)
previousScrollLeft = scroller.scrollLeft
expect(component.getScrollTop()).toBeLessThan(previousScrollTop)
previousScrollTop = component.getScrollTop()
expect(component.getScrollLeft()).toBe(previousScrollLeft)
previousScrollLeft = component.getScrollLeft()
}
component.didMouseDownOnLineNumberGutter({detail: 1, button: 0, clientX: 0, clientY: 100})
@ -1425,25 +1406,23 @@ describe('TextEditorComponent', () => {
// Don't artificially update scroll measurements beyond the minimum or
// maximum possible scroll positions
expect(scroller.scrollTop).toBe(0)
expect(scroller.scrollLeft).toBe(0)
expect(component.getScrollTop()).toBe(0)
expect(component.getScrollLeft()).toBe(0)
didDrag({clientX: component.getGutterContainerWidth() + 1, clientY: 1})
expect(component.measurements.scrollTop).toBe(0)
expect(scroller.scrollTop).toBe(0)
expect(component.measurements.scrollLeft).toBe(0)
expect(scroller.scrollLeft).toBe(0)
expect(component.getScrollTop()).toBe(0)
expect(component.getScrollLeft()).toBe(0)
const maxScrollTop = scroller.scrollHeight - scroller.clientHeight
const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth
scroller.scrollTop = maxScrollTop
scroller.scrollLeft = maxScrollLeft
const maxScrollTop = component.getMaxScrollTop()
const maxScrollLeft = component.getMaxScrollLeft()
component.setScrollTop(maxScrollTop)
component.setScrollLeft(maxScrollLeft)
await component.getNextUpdatePromise()
didDrag({clientX: 199, clientY: 199})
didDrag({clientX: 199, clientY: 199})
didDrag({clientX: 199, clientY: 199})
expect(component.measurements.scrollTop).toBe(maxScrollTop)
expect(component.measurements.scrollLeft).toBe(maxScrollLeft)
expect(component.getScrollTop()).toBe(maxScrollTop)
expect(component.getScrollLeft()).toBe(maxScrollLeft)
})
})
})
@ -1475,10 +1454,7 @@ function buildComponent (params = {}) {
}
function getBaseCharacterWidth (component) {
return Math.round(
(component.refs.scroller.clientWidth - component.getGutterContainerWidth()) /
component.measurements.baseCharacterWidth
)
return Math.round(component.getScrollContainerWidth() / component.getBaseCharacterWidth())
}
async function setEditorHeightInLines(component, heightInLines) {

View File

@ -16,6 +16,7 @@ const KOREAN_CHARACTER = '세'
const NBSP_CHARACTER = '\u00a0'
const ZERO_WIDTH_NBSP_CHARACTER = '\ufeff'
const MOUSE_DRAG_AUTOSCROLL_MARGIN = 40
const MOUSE_WHEEL_SCROLL_SENSITIVITY = 0.8
function scaleMouseDragAutoscrollDelta (delta) {
return Math.pow(delta / 3, 3) / 280
@ -47,7 +48,8 @@ class TextEditorComponent {
this.lineNodesByScreenLineId = new Map()
this.textNodesByScreenLineId = new Map()
this.pendingAutoscroll = null
this.autoscrollTop = null
this.scrollTop = 0
this.scrollLeft = 0
this.previousScrollWidth = 0
this.previousScrollHeight = 0
this.lastKeydown = null
@ -97,7 +99,7 @@ class TextEditorComponent {
}
this.horizontalPositionsToMeasure.clear()
if (this.pendingAutoscroll) this.initiateAutoscroll()
if (this.pendingAutoscroll) this.autoscrollVertically()
this.populateVisibleRowRange()
const longestLineToMeasure = this.checkForNewLongestLine()
this.queryScreenLinesToRender()
@ -111,19 +113,10 @@ class TextEditorComponent {
etch.updateSync(this)
// If scrollHeight or scrollWidth changed, we may have shown or hidden
// scrollbars, affecting the clientWidth or clientHeight
if (this.checkIfScrollDimensionsChanged()) {
this.measureClientDimensions()
// If the clientHeight changed, our previous vertical autoscroll may have
// been off by the height of the horizontal scrollbar. If we *still* need
// to autoscroll, just re-render the frame.
if (this.pendingAutoscroll && this.initiateAutoscroll()) {
this.updateSync()
return
}
if (this.pendingAutoscroll) {
this.autoscrollHorizontally()
this.pendingAutoscroll = null
}
if (this.pendingAutoscroll) this.finalizeAutoscroll()
this.currentFrameLineNumberGutterProps = null
}
@ -142,103 +135,82 @@ class TextEditorComponent {
render () {
const {model} = this.props
const style = {
overflow: 'hidden',
}
const style = {}
if (!model.getAutoHeight() && !model.getAutoWidth()) {
style.contain = 'strict'
}
if (this.measurements) {
if (model.getAutoHeight()) {
style.height = this.getScrollHeight() + 'px'
style.height = this.getContentHeight() + 'px'
}
if (model.getAutoWidth()) {
style.width = this.getScrollWidth() + 'px'
style.width = this.getGutterContainerWidth() + this.getContentWidth() + 'px'
}
}
let attributes = null
let className = 'editor'
if (this.focused) {
className += ' is-focused'
}
if (this.focused) className += ' is-focused'
if (model.isMini()) {
attributes = {mini: ''}
className += ' mini'
}
const scrollerOverflowX = (model.isMini() || model.isSoftWrapped()) ? 'hidden' : 'auto'
const scrollerOverflowY = model.isMini() ? 'hidden' : 'auto'
return $('atom-text-editor',
{
className,
attributes,
style,
attributes,
tabIndex: -1,
on: {
focus: this.didFocus,
blur: this.didBlur
blur: this.didBlur,
mousewheel: this.didMouseWheel
}
},
$.div(
{
ref: 'clientContainer',
style: {
position: 'relative',
contain: 'strict',
overflow: 'hidden',
backgroundColor: 'inherit',
width: '100%',
height: '100%',
backgroundColor: 'inherit'
height: '100%'
}
},
$.div(
{
ref: 'scroller',
className: 'scroll-view',
on: {scroll: this.didScroll},
style: {
position: 'absolute',
contain: 'strict',
top: 0,
right: 0,
bottom: 0,
left: 0,
overflowX: scrollerOverflowX,
overflowY: scrollerOverflowY,
backgroundColor: 'inherit'
}
},
$.div(
{
style: {
isolate: 'content',
width: 'max-content',
height: 'max-content',
backgroundColor: 'inherit'
}
},
this.renderGutterContainer(),
this.renderContent()
)
)
this.renderGutterContainer(),
this.renderScrollContainer()
)
)
}
renderGutterContainer () {
if (this.props.model.isMini()) return null
const props = {ref: 'gutterContainer', className: 'gutter-container'}
const innerStyle = {
willChange: 'transform',
backgroundColor: 'inherit'
}
if (this.measurements) {
props.style = {
position: 'relative',
willChange: 'transform',
transform: `translateX(${this.measurements.scrollLeft}px)`,
zIndex: 1
}
innerStyle.transform = `translateY(${-this.getScrollTop()}px)`
}
return $.div(props, this.renderLineNumberGutter())
return $.div(
{
ref: 'gutterContainer',
className: 'gutter-container',
style: {
position: 'relative',
zIndex: 1,
backgroundColor: 'inherit'
}
},
$.div({style: innerStyle},
this.renderLineNumberGutter()
)
)
}
renderLineNumberGutter () {
@ -278,8 +250,8 @@ class TextEditorComponent {
ref: 'lineNumberGutter',
parentComponent: this,
height: this.getScrollHeight(),
width: this.measurements.lineNumberGutterWidth,
lineHeight: this.measurements.lineHeight,
width: this.getLineNumberGutterWidth(),
lineHeight: this.getLineHeight(),
startRow, endRow, rowsPerTile, maxLineNumberDigits,
bufferRows, lineNumberDecorations, softWrappedFlags,
foldableFlags
@ -301,6 +273,31 @@ class TextEditorComponent {
}
}
renderScrollContainer () {
const style = {
position: 'absolute',
contain: 'strict',
overflow: 'hidden',
top: 0,
bottom: 0,
backgroundColor: 'inherit'
}
if (this.measurements) {
style.left = this.getGutterContainerWidth() + 'px'
style.width = this.getScrollContainerWidth() + 'px'
}
return $.div(
{
ref: 'scrollContainer',
className: 'scroll-view',
style
},
this.renderContent()
)
}
renderContent () {
let children
let style = {
@ -309,15 +306,13 @@ class TextEditorComponent {
backgroundColor: 'inherit'
}
if (this.measurements) {
const contentWidth = this.getContentWidth()
const scrollHeight = this.getScrollHeight()
const width = contentWidth + 'px'
const height = scrollHeight + 'px'
style.width = width
style.height = height
style.width = this.getScrollWidth() + 'px'
style.height = this.getScrollHeight() + 'px'
style.willChange = 'transform'
style.transform = `translate(${-this.getScrollLeft()}px, ${-this.getScrollTop()}px)`
children = [
this.renderCursorsAndInput(width, height),
this.renderLineTiles(width, height),
this.renderCursorsAndInput(),
this.renderLineTiles(),
this.renderPlaceholderText()
]
} else {
@ -339,7 +334,7 @@ class TextEditorComponent {
)
}
renderLineTiles (width, height) {
renderLineTiles () {
if (!this.measurements) return []
const {lineNodesByScreenLineId, textNodesByScreenLineId} = this
@ -347,8 +342,8 @@ class TextEditorComponent {
const startRow = this.getRenderedStartRow()
const endRow = this.getRenderedEndRow()
const rowsPerTile = this.getRowsPerTile()
const tileHeight = this.measurements.lineHeight * rowsPerTile
const tileWidth = this.getContentWidth()
const tileHeight = this.getLineHeight() * rowsPerTile
const tileWidth = this.getScrollWidth()
const displayLayer = this.props.model.displayLayer
const tileNodes = new Array(this.getRenderedTileCount())
@ -368,7 +363,7 @@ class TextEditorComponent {
height: tileHeight,
width: tileWidth,
top: this.topPixelPositionForRow(tileStartRow),
lineHeight: this.measurements.lineHeight,
lineHeight: this.getLineHeight(),
screenLines: this.renderedScreenLines.slice(tileStartRow - startRow, tileEndRow - startRow),
lineDecorations,
highlightDecorations,
@ -395,14 +390,16 @@ class TextEditorComponent {
style: {
position: 'absolute',
contain: 'strict',
width, height,
overflow: 'hidden',
width: this.getScrollWidth() + 'px',
height: this.getScrollHeight() + 'px',
backgroundColor: 'inherit'
}
}, tileNodes)
}
renderCursorsAndInput (width, height) {
const cursorHeight = this.measurements.lineHeight + 'px'
renderCursorsAndInput () {
const cursorHeight = this.getLineHeight() + 'px'
const children = [this.renderHiddenInput()]
@ -425,7 +422,8 @@ class TextEditorComponent {
position: 'absolute',
contain: 'strict',
zIndex: 1,
width, height
width: this.getScrollWidth() + 'px',
height: this.getScrollHeight() + 'px'
}
}, children)
}
@ -470,7 +468,7 @@ class TextEditorComponent {
style: {
position: 'absolute',
width: '1px',
height: this.measurements.lineHeight + 'px',
height: this.getLineHeight() + 'px',
top: top + 'px',
left: left + 'px',
opacity: 0,
@ -646,7 +644,7 @@ class TextEditorComponent {
updateCursorsToRender () {
this.decorationsToRender.cursors.length = 0
const height = this.measurements.lineHeight + 'px'
const height = this.getLineHeight() + 'px'
for (let i = 0; i < this.decorationsToMeasure.cursors.length; i++) {
const cursor = this.decorationsToMeasure.cursors[i]
const {row, column} = cursor.screenPosition
@ -765,15 +763,20 @@ class TextEditorComponent {
}
}
didScroll () {
if (this.measureScrollPosition(true)) {
this.updateSync()
}
didMouseWheel (eveWt) {
let {deltaX, deltaY} = event
deltaX = deltaX * MOUSE_WHEEL_SCROLL_SENSITIVITY
deltaY = deltaY * MOUSE_WHEEL_SCROLL_SENSITIVITY
const scrollPositionChanged =
this.setScrollLeft(this.getScrollLeft() + deltaX) ||
this.setScrollTop(this.getScrollTop() + deltaY)
if (scrollPositionChanged) this.updateSync()
}
didResize () {
if (this.measureEditorDimensions()) {
this.measureClientDimensions()
if (this.measureClientContainerDimensions()) {
this.scheduleUpdate()
}
}
@ -1023,10 +1026,10 @@ class TextEditorComponent {
}
autoscrollOnMouseDrag ({clientX, clientY}, verticalOnly = false) {
let {top, bottom, left, right} = this.refs.scroller.getBoundingClientRect()
let {top, bottom, left, right} = this.refs.scrollContainer.getBoundingClientRect()
top += MOUSE_DRAG_AUTOSCROLL_MARGIN
bottom -= MOUSE_DRAG_AUTOSCROLL_MARGIN
left += this.getGutterContainerWidth() + MOUSE_DRAG_AUTOSCROLL_MARGIN
left += MOUSE_DRAG_AUTOSCROLL_MARGIN
right -= MOUSE_DRAG_AUTOSCROLL_MARGIN
let yDelta, yDirection
@ -1050,31 +1053,21 @@ class TextEditorComponent {
let scrolled = false
if (yDelta != null) {
const scaledDelta = scaleMouseDragAutoscrollDelta(yDelta) * yDirection
const newScrollTop = this.constrainScrollTop(this.measurements.scrollTop + scaledDelta)
if (newScrollTop !== this.measurements.scrollTop) {
this.measurements.scrollTop = newScrollTop
this.refs.scroller.scrollTop = newScrollTop
scrolled = true
}
scrolled = this.setScrollTop(this.getScrollTop() + scaledDelta)
}
if (!verticalOnly && xDelta != null) {
const scaledDelta = scaleMouseDragAutoscrollDelta(xDelta) * xDirection
const newScrollLeft = this.constrainScrollLeft(this.measurements.scrollLeft + scaledDelta)
if (newScrollLeft !== this.measurements.scrollLeft) {
this.measurements.scrollLeft = newScrollLeft
this.refs.scroller.scrollLeft = newScrollLeft
scrolled = true
}
scrolled = this.setScrollLeft(this.getScrollLeft() + scaledDelta)
}
if (scrolled) this.updateSync()
}
screenPositionForMouseEvent ({clientX, clientY}) {
const scrollerRect = this.refs.scroller.getBoundingClientRect()
clientX = Math.min(scrollerRect.right, Math.max(scrollerRect.left, clientX))
clientY = Math.min(scrollerRect.bottom, Math.max(scrollerRect.top, clientY))
const scrollContainerRect = this.refs.scrollContainer.getBoundingClientRect()
clientX = Math.min(scrollContainerRect.right, Math.max(scrollContainerRect.left, clientX))
clientY = Math.min(scrollContainerRect.bottom, Math.max(scrollContainerRect.top, clientY))
const linesRect = this.refs.lineTiles.getBoundingClientRect()
return this.screenPositionForPixelPosition({
top: clientY - linesRect.top,
@ -1087,12 +1080,12 @@ class TextEditorComponent {
this.scheduleUpdate()
}
initiateAutoscroll () {
autoscrollVertically () {
const {screenRange, options} = this.pendingAutoscroll
const screenRangeTop = this.pixelTopForRow(screenRange.start.row)
const screenRangeBottom = this.pixelTopForRow(screenRange.end.row) + this.measurements.lineHeight
const verticalScrollMargin = this.getVerticalScrollMargin()
const screenRangeBottom = this.pixelTopForRow(screenRange.end.row) + this.getLineHeight()
const verticalScrollMargin = this.getVerticalAutoscrollMargin()
this.requestHorizontalMeasurement(screenRange.start.row, screenRange.start.column)
this.requestHorizontalMeasurement(screenRange.end.row, screenRange.end.column)
@ -1109,43 +1102,27 @@ class TextEditorComponent {
desiredScrollBottom = screenRangeBottom + verticalScrollMargin
}
if (desiredScrollTop != null) {
desiredScrollTop = this.constrainScrollTop(desiredScrollTop)
}
if (desiredScrollBottom != null) {
desiredScrollBottom = this.constrainScrollTop(desiredScrollBottom - this.getClientHeight()) + this.getClientHeight()
}
if (!options || options.reversed !== false) {
if (desiredScrollBottom > this.getScrollBottom()) {
this.autoscrollTop = desiredScrollBottom - this.measurements.clientHeight
this.measurements.scrollTop = this.autoscrollTop
return true
return this.setScrollBottom(desiredScrollBottom, true)
}
if (desiredScrollTop < this.getScrollTop()) {
this.autoscrollTop = desiredScrollTop
this.measurements.scrollTop = this.autoscrollTop
return true
return this.setScrollTop(desiredScrollTop, true)
}
} else {
if (desiredScrollTop < this.getScrollTop()) {
this.autoscrollTop = desiredScrollTop
this.measurements.scrollTop = this.autoscrollTop
return true
return this.setScrollTop(desiredScrollTop, true)
}
if (desiredScrollBottom > this.getScrollBottom()) {
this.autoscrollTop = desiredScrollBottom - this.measurements.clientHeight
this.measurements.scrollTop = this.autoscrollTop
return true
return this.setScrollBottom(desiredScrollBottom, true)
}
}
return false
}
finalizeAutoscroll () {
const horizontalScrollMargin = this.getHorizontalScrollMargin()
autoscrollHorizontally () {
const horizontalScrollMargin = this.getHorizontalAutoscrollMargin()
const {screenRange, options} = this.pendingAutoscroll
const gutterContainerWidth = this.getGutterContainerWidth()
@ -1154,121 +1131,70 @@ class TextEditorComponent {
const desiredScrollLeft = Math.max(0, left - horizontalScrollMargin - gutterContainerWidth)
const desiredScrollRight = Math.min(this.getScrollWidth(), right + horizontalScrollMargin)
let autoscrollLeft
if (!options || options.reversed !== false) {
if (desiredScrollRight > this.getScrollRight()) {
autoscrollLeft = desiredScrollRight - this.getClientWidth()
this.measurements.scrollLeft = autoscrollLeft
this.setScrollRight(desiredScrollRight, true)
}
if (desiredScrollLeft < this.getScrollLeft()) {
autoscrollLeft = desiredScrollLeft
this.measurements.scrollLeft = autoscrollLeft
this.setScrollLeft(desiredScrollLeft, true)
}
} else {
if (desiredScrollLeft < this.getScrollLeft()) {
autoscrollLeft = desiredScrollLeft
this.measurements.scrollLeft = autoscrollLeft
this.setScrollLeft(desiredScrollLeft, true)
}
if (desiredScrollRight > this.getScrollRight()) {
autoscrollLeft = desiredScrollRight - this.getClientWidth()
this.measurements.scrollLeft = autoscrollLeft
this.setScrollRight(desiredScrollRight, true)
}
}
if (this.autoscrollTop != null) {
this.refs.scroller.scrollTop = this.autoscrollTop
this.autoscrollTop = null
}
if (autoscrollLeft != null) {
this.refs.scroller.scrollLeft = autoscrollLeft
}
this.pendingAutoscroll = null
}
getVerticalScrollMargin () {
const {clientHeight, lineHeight} = this.measurements
getVerticalAutoscrollMargin () {
const maxMarginInLines = Math.floor(
(this.getScrollContainerClientHeight() / this.getLineHeight() - 1) / 2
)
const marginInLines = Math.min(
this.props.model.verticalScrollMargin,
Math.floor(((clientHeight / lineHeight) - 1) / 2)
maxMarginInLines
)
return marginInLines * lineHeight
return marginInLines * this.getLineHeight()
}
getHorizontalScrollMargin () {
const {clientWidth, baseCharacterWidth} = this.measurements
const contentClientWidth = clientWidth - this.getGutterContainerWidth()
getHorizontalAutoscrollMargin () {
const maxMarginInBaseCharacters = Math.floor(
(this.getScrollContainerClientWidth() / this.getBaseCharacterWidth() - 1) / 2
)
const marginInBaseCharacters = Math.min(
this.props.model.horizontalScrollMargin,
Math.floor(((contentClientWidth / baseCharacterWidth) - 1) / 2)
)
return marginInBaseCharacters * baseCharacterWidth
}
constrainScrollTop (desiredScrollTop) {
return Math.max(
0, Math.min(desiredScrollTop, this.getScrollHeight() - this.getClientHeight())
)
}
constrainScrollLeft (desiredScrollLeft) {
return Math.max(
0, Math.min(desiredScrollLeft, this.getScrollWidth() - this.getClientWidth())
maxMarginInBaseCharacters
)
return marginInBaseCharacters * this.getBaseCharacterWidth()
}
performInitialMeasurements () {
this.measurements = {}
this.measureGutterDimensions()
this.measureEditorDimensions()
this.measureClientDimensions()
this.measureScrollPosition()
this.measureCharacterDimensions()
this.measureGutterDimensions()
this.measureClientContainerDimensions()
}
measureEditorDimensions () {
measureClientContainerDimensions () {
if (!this.measurements) return false
let dimensionsChanged = false
const scrollerHeight = this.refs.scroller.offsetHeight
const scrollerWidth = this.refs.scroller.offsetWidth
if (scrollerHeight !== this.measurements.scrollerHeight) {
this.measurements.scrollerHeight = scrollerHeight
const clientContainerHeight = this.refs.clientContainer.offsetHeight
const clientContainerWidth = this.refs.clientContainer.offsetWidth
if (clientContainerHeight !== this.measurements.clientContainerHeight) {
this.measurements.clientContainerHeight = clientContainerHeight
dimensionsChanged = true
}
if (scrollerWidth !== this.measurements.scrollerWidth) {
this.measurements.scrollerWidth = scrollerWidth
if (clientContainerWidth !== this.measurements.clientContainerWidth) {
this.measurements.clientContainerWidth = clientContainerWidth
this.props.model.setEditorWidthInChars(this.getScrollContainerWidth() / this.getBaseCharacterWidth())
dimensionsChanged = true
}
return dimensionsChanged
}
measureScrollPosition () {
let scrollPositionChanged = false
const {scrollTop, scrollLeft} = this.refs.scroller
if (scrollTop !== this.measurements.scrollTop) {
this.measurements.scrollTop = scrollTop
scrollPositionChanged = true
}
if (scrollLeft !== this.measurements.scrollLeft) {
this.measurements.scrollLeft = scrollLeft
scrollPositionChanged = true
}
return scrollPositionChanged
}
measureClientDimensions () {
const {clientHeight, clientWidth} = this.refs.scroller
if (clientHeight !== this.measurements.clientHeight) {
this.measurements.clientHeight = clientHeight
}
if (clientWidth !== this.measurements.clientWidth) {
this.measurements.clientWidth = clientWidth
this.props.model.setWidth(clientWidth - this.getGutterContainerWidth(), true)
}
}
measureCharacterDimensions () {
this.measurements.lineHeight = this.refs.characterMeasurementLine.getBoundingClientRect().height
this.measurements.baseCharacterWidth = this.refs.normalWidthCharacterSpan.getBoundingClientRect().width
@ -1383,7 +1309,7 @@ class TextEditorComponent {
}
pixelTopForRow (row) {
return row * this.measurements.lineHeight
return row * this.getLineHeight()
}
pixelLeftForRowAndColumn (row, column) {
@ -1478,73 +1404,91 @@ class TextEditorComponent {
return this.element.offsetWidth > 0 || this.element.offsetHeight > 0
}
getLineHeight () {
return this.measurements.lineHeight
}
getBaseCharacterWidth () {
return this.measurements ? this.measurements.baseCharacterWidth : null
}
getScrollTop () {
if (this.measurements != null) {
return this.measurements.scrollTop
getLongestLineWidth () {
return this.measurements.longestLineWidth
}
getClientContainerHeight () {
return this.measurements.clientContainerHeight
}
getClientContainerWidth () {
return this.measurements.clientContainerWidth
}
getScrollContainerWidth () {
if (this.props.model.getAutoWidth()) {
return this.getScrollWidth()
} else {
return this.getClientContainerWidth() - this.getGutterContainerWidth()
}
}
getScrollBottom () {
return this.measurements
? this.measurements.scrollTop + this.measurements.clientHeight
: null
getScrollContainerHeight () {
if (this.props.model.getAutoHeight()) {
return this.getScrollHeight()
} else {
return this.getClientContainerHeight()
}
}
getScrollLeft () {
return this.measurements ? this.measurements.scrollLeft : null
getScrollContainerHeightInLines () {
return Math.ceil(this.getScrollContainerHeight() / this.getLineHeight())
}
getScrollRight () {
return this.measurements
? this.measurements.scrollLeft + this.measurements.clientWidth
: null
getScrollContainerClientWidth () {
return this.getScrollContainerWidth()
}
getScrollContainerClientHeight () {
return this.getScrollContainerHeight()
}
getScrollHeight () {
const {model} = this.props
const contentHeight = model.getApproximateScreenLineCount() * this.measurements.lineHeight
if (model.getScrollPastEnd()) {
const extraScrollHeight = Math.max(
3 * this.measurements.lineHeight,
this.getClientHeight() - 3 * this.measurements.lineHeight
if (this.props.model.getScrollPastEnd()) {
return this.getContentHeight() + Math.max(
3 * this.getLineHeight(),
this.getScrollContainerClientHeight() - (3 * this.getLineHeight())
)
return contentHeight + extraScrollHeight
} else {
return contentHeight
return this.getContentHeight()
}
}
getScrollWidth () {
return this.getContentWidth() + this.getGutterContainerWidth()
const {model} = this.props
if (model.isSoftWrapped()) {
return this.getScrollContainerClientWidth()
} else if (model.getAutoWidth()) {
return this.getContentWidth()
} else {
return Math.max(this.getContentWidth(), this.getScrollContainerClientWidth())
}
}
getClientHeight () {
return this.measurements.clientHeight
}
getClientWidth () {
return this.measurements.clientWidth
}
getGutterContainerWidth () {
return this.measurements.lineNumberGutterWidth
getContentHeight () {
return this.props.model.getApproximateScreenLineCount() * this.getLineHeight()
}
getContentWidth () {
if (this.props.model.isSoftWrapped()) {
return this.getClientWidth() - this.getGutterContainerWidth()
} else if (this.props.model.getAutoWidth()) {
return Math.round(this.measurements.longestLineWidth + this.measurements.baseCharacterWidth)
} else {
return Math.max(
Math.round(this.measurements.longestLineWidth + this.measurements.baseCharacterWidth),
this.measurements.scrollerWidth - this.getGutterContainerWidth()
)
}
return Math.round(this.getLongestLineWidth() + this.getBaseCharacterWidth())
}
getGutterContainerWidth () {
return this.getLineNumberGutterWidth()
}
getLineNumberGutterWidth () {
return this.measurements.lineNumberGutterWidth
}
getRowsPerTile () {
@ -1583,16 +1527,13 @@ class TextEditorComponent {
}
getFirstVisibleRow () {
const scrollTop = this.getScrollTop()
const lineHeight = this.measurements.lineHeight
return Math.floor(scrollTop / lineHeight)
return Math.floor(this.getScrollTop() / this.getLineHeight())
}
getLastVisibleRow () {
const {scrollerHeight, lineHeight} = this.measurements
return Math.min(
this.props.model.getApproximateScreenLineCount() - 1,
this.getFirstVisibleRow() + Math.ceil(scrollerHeight / lineHeight)
this.getFirstVisibleRow() + this.getScrollContainerHeightInLines()
)
}
@ -1600,6 +1541,63 @@ class TextEditorComponent {
return Math.floor((this.getLastVisibleRow() - this.getFirstVisibleRow()) / this.getRowsPerTile()) + 2
}
getScrollTop () {
this.scrollTop = Math.min(this.getMaxScrollTop(), this.scrollTop)
return this.scrollTop
}
setScrollTop (scrollTop, suppressUpdate = false) {
scrollTop = Math.round(Math.max(0, Math.min(this.getMaxScrollTop(), scrollTop)))
if (scrollTop !== this.scrollTop) {
this.scrollTop = scrollTop
if (!suppressUpdate) this.scheduleUpdate()
return true
} else {
return false
}
}
getMaxScrollTop () {
return Math.max(0, this.getScrollHeight() - this.getScrollContainerClientHeight())
}
getScrollBottom () {
return this.getScrollTop() + this.getScrollContainerClientHeight()
}
setScrollBottom (scrollBottom, suppressUpdate = false) {
return this.setScrollTop(scrollBottom - this.getScrollContainerClientHeight(), suppressUpdate)
}
getScrollLeft () {
// this.scrollLeft = Math.min(this.getMaxScrollLeft(), this.scrollLeft)
return this.scrollLeft
}
setScrollLeft (scrollLeft, suppressUpdate = false) {
scrollLeft = Math.round(Math.max(0, Math.min(this.getMaxScrollLeft(), scrollLeft)))
if (scrollLeft !== this.scrollLeft) {
this.scrollLeft = scrollLeft
if (!suppressUpdate) this.scheduleUpdate()
return true
} else {
return false
}
}
getMaxScrollLeft () {
return Math.max(0, this.getScrollWidth() - this.getScrollContainerClientWidth())
}
getScrollRight () {
return this.getScrollLeft() + this.getScrollContainerClientWidth()
}
setScrollRight (scrollRight, suppressUpdate = false) {
return this.setScrollLeft(scrollRight - this.getScrollContainerClientWidth(), suppressUpdate)
}
// Ensure the spatial index is populated with rows that are currently
// visible so we *at least* get the longest row in the visible range.
populateVisibleRowRange () {
@ -1608,7 +1606,7 @@ class TextEditorComponent {
}
topPixelPositionForRow (row) {
return row * this.measurements.lineHeight
return row * this.getLineHeight()
}
getNextUpdatePromise () {
@ -1829,7 +1827,7 @@ class LinesTileComponent {
position: 'absolute',
contain: 'strict',
height: height + 'px',
width: width + 'px',
width: width + 'px'
}
}, children)
}

View File

@ -18,7 +18,7 @@ class TextEditorElement extends HTMLElement {
}
getModel () {
return this.getComponent().getModel()
return this.getComponent().props.model
}
setModel (model) {