mirror of
https://github.com/atom/atom.git
synced 2026-01-23 05:48:10 -05:00
Merge pull request #6733 from atom/as-tiled-rendering
Render lines via tiles
This commit is contained in:
@@ -7,10 +7,10 @@ nbsp = String.fromCharCode(160)
|
||||
|
||||
describe "TextEditorComponent", ->
|
||||
[contentNode, editor, wrapperView, wrapperNode, component, componentNode, verticalScrollbarNode, horizontalScrollbarNode] = []
|
||||
[lineHeightInPixels, charWidth, nextAnimationFrame, noAnimationFrame, lineOverdrawMargin] = []
|
||||
[lineHeightInPixels, charWidth, nextAnimationFrame, noAnimationFrame, tileSize] = []
|
||||
|
||||
beforeEach ->
|
||||
lineOverdrawMargin = 2
|
||||
tileSize = 3
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
@@ -34,7 +34,7 @@ describe "TextEditorComponent", ->
|
||||
contentNode = document.querySelector('#jasmine-content')
|
||||
contentNode.style.width = '1000px'
|
||||
|
||||
wrapperView = new TextEditorView(editor, {lineOverdrawMargin})
|
||||
wrapperView = new TextEditorView(editor, {tileSize})
|
||||
wrapperView.attachToDom()
|
||||
wrapperNode = wrapperView.element
|
||||
wrapperNode.setUpdatedSynchronously(false)
|
||||
@@ -68,48 +68,111 @@ describe "TextEditorComponent", ->
|
||||
expect(nextAnimationFrame).not.toThrow()
|
||||
|
||||
describe "line rendering", ->
|
||||
it "renders the currently-visible lines plus the overdraw margin", ->
|
||||
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
expectTileContainsRow = (tileNode, screenRow, {top}) ->
|
||||
lineNode = tileNode.querySelector("[data-screen-row='#{screenRow}']")
|
||||
tokenizedLine = editor.tokenizedLineForScreenRow(screenRow)
|
||||
|
||||
expect(lineNode.offsetTop).toBe(top)
|
||||
if tokenizedLine.text is ""
|
||||
expect(lineNode.innerHTML).toBe(" ")
|
||||
else
|
||||
expect(lineNode.textContent).toBe(tokenizedLine.text)
|
||||
|
||||
it "renders the currently-visible lines in a tiled fashion", ->
|
||||
wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px'
|
||||
tileHeight = tileSize * lineHeightInPixels
|
||||
component.measureDimensions()
|
||||
nextAnimationFrame()
|
||||
|
||||
linesNode = componentNode.querySelector('.lines')
|
||||
expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
|
||||
expect(componentNode.querySelectorAll('.line').length).toBe 6 + 2 # no margin above
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe editor.tokenizedLineForScreenRow(0).text
|
||||
expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0
|
||||
expect(component.lineNodeForScreenRow(5).textContent).toBe editor.tokenizedLineForScreenRow(5).text
|
||||
expect(component.lineNodeForScreenRow(5).offsetTop).toBe 5 * lineHeightInPixels
|
||||
tilesNodes = componentNode.querySelectorAll(".tile")
|
||||
|
||||
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
|
||||
expect(tilesNodes.length).toBe(3)
|
||||
|
||||
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
|
||||
expect(tilesNodes[0].children.length).toBe(tileSize)
|
||||
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)
|
||||
|
||||
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
|
||||
expect(tilesNodes[1].children.length).toBe(tileSize)
|
||||
expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)
|
||||
|
||||
expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)"
|
||||
expect(tilesNodes[2].children.length).toBe(tileSize)
|
||||
expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels)
|
||||
|
||||
expect(component.lineNodeForScreenRow(9)).toBeUndefined()
|
||||
|
||||
verticalScrollbarNode.scrollTop = tileSize * lineHeightInPixels + 5
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
nextAnimationFrame()
|
||||
|
||||
expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, #{-4.5 * lineHeightInPixels}px, 0px)"
|
||||
expect(componentNode.querySelectorAll('.line').length).toBe 6 + 4 # margin above and below
|
||||
expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
|
||||
expect(component.lineNodeForScreenRow(2).textContent).toBe editor.tokenizedLineForScreenRow(2).text
|
||||
expect(component.lineNodeForScreenRow(9).offsetTop).toBe 9 * lineHeightInPixels
|
||||
expect(component.lineNodeForScreenRow(9).textContent).toBe editor.tokenizedLineForScreenRow(9).text
|
||||
tilesNodes = componentNode.querySelectorAll(".tile")
|
||||
|
||||
it "updates the top position of subsequent lines when lines are inserted or removed", ->
|
||||
expect(component.lineNodeForScreenRow(2)).toBeUndefined()
|
||||
expect(tilesNodes.length).toBe(3)
|
||||
|
||||
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, -5px, 0px)"
|
||||
expect(tilesNodes[0].children.length).toBe(tileSize)
|
||||
expectTileContainsRow(tilesNodes[0], 3, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[0], 4, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[0], 5, top: 2 * lineHeightInPixels)
|
||||
|
||||
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight - 5}px, 0px)"
|
||||
expect(tilesNodes[1].children.length).toBe(tileSize)
|
||||
expectTileContainsRow(tilesNodes[1], 6, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[1], 7, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[1], 8, top: 2 * lineHeightInPixels)
|
||||
|
||||
expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight - 5}px, 0px)"
|
||||
expect(tilesNodes[2].children.length).toBe(tileSize)
|
||||
expectTileContainsRow(tilesNodes[2], 9, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[2], 10, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[2], 11, top: 2 * lineHeightInPixels)
|
||||
|
||||
it "updates the top position of subsequent tiles when lines are inserted or removed", ->
|
||||
wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px'
|
||||
tileHeight = tileSize * lineHeightInPixels
|
||||
component.measureDimensions()
|
||||
editor.getBuffer().deleteRows(0, 1)
|
||||
nextAnimationFrame()
|
||||
|
||||
lineNodes = componentNode.querySelectorAll('.line')
|
||||
expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0
|
||||
expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels
|
||||
expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
|
||||
tilesNodes = componentNode.querySelectorAll(".tile")
|
||||
|
||||
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
|
||||
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)
|
||||
|
||||
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
|
||||
expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n\n')
|
||||
nextAnimationFrame()
|
||||
|
||||
lineNodes = componentNode.querySelectorAll('.line')
|
||||
expect(component.lineNodeForScreenRow(0).offsetTop).toBe 0 * lineHeightInPixels
|
||||
expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels
|
||||
expect(component.lineNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
|
||||
expect(component.lineNodeForScreenRow(3).offsetTop).toBe 3 * lineHeightInPixels
|
||||
expect(component.lineNodeForScreenRow(4).offsetTop).toBe 4 * lineHeightInPixels
|
||||
tilesNodes = componentNode.querySelectorAll(".tile")
|
||||
|
||||
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
|
||||
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)
|
||||
|
||||
expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
|
||||
expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)
|
||||
|
||||
expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)"
|
||||
expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels)
|
||||
expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels)
|
||||
|
||||
it "updates the lines when lines are inserted or removed above the rendered row range", ->
|
||||
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
@@ -483,7 +546,7 @@ describe "TextEditorComponent", ->
|
||||
component.measureDimensions()
|
||||
nextAnimationFrame()
|
||||
|
||||
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 2 + 1 # line overdraw margin below + dummy line number
|
||||
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # visible line-numbers + dummy line number
|
||||
expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1"
|
||||
expect(component.lineNumberNodeForScreenRow(5).textContent).toBe "#{nbsp}6"
|
||||
|
||||
@@ -491,7 +554,7 @@ describe "TextEditorComponent", ->
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
nextAnimationFrame()
|
||||
|
||||
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 4 + 1 # line overdraw margin above/below + dummy line number
|
||||
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # visible line-numbers + dummy line number
|
||||
|
||||
expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}3"
|
||||
expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels
|
||||
@@ -527,7 +590,7 @@ describe "TextEditorComponent", ->
|
||||
component.measureDimensions()
|
||||
nextAnimationFrame()
|
||||
|
||||
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + lineOverdrawMargin + 1 # 1 dummy line componentNode
|
||||
expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # 1 dummy line
|
||||
expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1"
|
||||
expect(component.lineNumberNodeForScreenRow(1).textContent).toBe "#{nbsp}•"
|
||||
expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}2"
|
||||
@@ -725,13 +788,13 @@ describe "TextEditorComponent", ->
|
||||
|
||||
cursorNodes = componentNode.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 2
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)"
|
||||
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)"
|
||||
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)"
|
||||
|
||||
editor.onDidChangeCursorPosition cursorMovedListener = jasmine.createSpy('cursorMovedListener')
|
||||
cursor3.setScreenPosition([4, 11], autoscroll: false)
|
||||
nextAnimationFrame()
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)"
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)"
|
||||
expect(cursorMovedListener).toHaveBeenCalled()
|
||||
|
||||
cursor3.destroy()
|
||||
@@ -739,7 +802,7 @@ describe "TextEditorComponent", ->
|
||||
cursorNodes = componentNode.querySelectorAll('.cursor')
|
||||
|
||||
expect(cursorNodes.length).toBe 1
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)"
|
||||
|
||||
it "accounts for character widths when positioning cursors", ->
|
||||
atom.config.set('editor.fontFamily', 'sans-serif')
|
||||
@@ -1001,7 +1064,7 @@ describe "TextEditorComponent", ->
|
||||
nextAnimationFrame()
|
||||
|
||||
# Scroll decorations into view
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
nextAnimationFrame()
|
||||
expect(lineAndLineNumberHaveClass(9, 'b')).toBe true
|
||||
@@ -1147,15 +1210,16 @@ describe "TextEditorComponent", ->
|
||||
# Nothing when outside the rendered row range
|
||||
expect(regions.length).toBe 0
|
||||
|
||||
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.scrollTop = 6 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
nextAnimationFrame()
|
||||
expect(component.presenter.endRow).toBeGreaterThan(8)
|
||||
|
||||
regions = componentNode.querySelectorAll('.some-highlight .region')
|
||||
|
||||
expect(regions.length).toBe 1
|
||||
regionRect = regions[0].style
|
||||
expect(regionRect.top).toBe 9 * lineHeightInPixels + 'px'
|
||||
expect(regionRect.top).toBe (9 * lineHeightInPixels - verticalScrollbarNode.scrollTop) + 'px'
|
||||
expect(regionRect.height).toBe 1 * lineHeightInPixels + 'px'
|
||||
expect(regionRect.left).toBe 2 * charWidth + 'px'
|
||||
expect(regionRect.width).toBe 2 * charWidth + 'px'
|
||||
@@ -1920,13 +1984,23 @@ describe "TextEditorComponent", ->
|
||||
component.measureDimensions()
|
||||
nextAnimationFrame()
|
||||
|
||||
linesNode = componentNode.querySelector('.lines')
|
||||
expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
|
||||
tilesNodes = componentNode.querySelectorAll(".tile")
|
||||
|
||||
top = 0
|
||||
for tileNode in tilesNodes
|
||||
expect(tileNode.style['-webkit-transform']).toBe "translate3d(0px, #{top}px, 0px)"
|
||||
top += tileNode.offsetHeight
|
||||
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 0
|
||||
|
||||
editor.setScrollLeft(100)
|
||||
nextAnimationFrame()
|
||||
expect(linesNode.style['-webkit-transform']).toBe "translate3d(-100px, 0px, 0px)"
|
||||
|
||||
top = 0
|
||||
for tileNode in tilesNodes
|
||||
expect(tileNode.style['-webkit-transform']).toBe "translate3d(-100px, #{top}px, 0px)"
|
||||
top += tileNode.offsetHeight
|
||||
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 100
|
||||
|
||||
it "updates the scrollLeft of the model when the scrollLeft of the horizontal scrollbar changes", ->
|
||||
@@ -2387,7 +2461,7 @@ describe "TextEditorComponent", ->
|
||||
hiddenParent.style.display = 'none'
|
||||
contentNode.appendChild(hiddenParent)
|
||||
|
||||
wrapperView = new TextEditorView(editor, {lineOverdrawMargin})
|
||||
wrapperView = new TextEditorView(editor, {tileSize})
|
||||
wrapperNode = wrapperView.element
|
||||
wrapperView.appendTo(hiddenParent)
|
||||
|
||||
@@ -2508,7 +2582,7 @@ describe "TextEditorComponent", ->
|
||||
|
||||
advanceClock(atom.views.documentPollingInterval)
|
||||
nextAnimationFrame()
|
||||
expect(componentNode.querySelectorAll('.line')).toHaveLength(4 + lineOverdrawMargin + 1)
|
||||
expect(componentNode.querySelectorAll('.line')).toHaveLength(6)
|
||||
|
||||
gutterWidth = componentNode.querySelector('.gutter').offsetWidth
|
||||
componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
|
||||
|
||||
@@ -39,7 +39,6 @@ describe "TextEditorPresenter", ->
|
||||
verticalScrollbarWidth: 10
|
||||
scrollTop: 0
|
||||
scrollLeft: 0
|
||||
lineOverdrawMargin: 0
|
||||
|
||||
new TextEditorPresenter(params)
|
||||
|
||||
@@ -657,160 +656,125 @@ describe "TextEditorPresenter", ->
|
||||
expectStateUpdate presenter, -> editor.setPlaceholderText("new-placeholder-text")
|
||||
expect(presenter.getState().content.placeholderText).toBe "new-placeholder-text"
|
||||
|
||||
describe ".lines", ->
|
||||
lineStateForScreenRow = (presenter, screenRow) ->
|
||||
presenter.getState().content.lines[presenter.model.tokenizedLineForScreenRow(screenRow).id]
|
||||
describe ".tiles", ->
|
||||
lineStateForScreenRow = (presenter, row) ->
|
||||
lineId = presenter.model.tokenizedLineForScreenRow(row).id
|
||||
tileRow = presenter.tileForRow(row)
|
||||
presenter.getState().content.tiles[tileRow]?.lines[lineId]
|
||||
|
||||
it "contains states for lines that are visible on screen, plus and minus the overdraw margin", ->
|
||||
presenter = buildPresenter(explicitHeight: 15, scrollTop: 50, lineHeight: 10, lineOverdrawMargin: 1)
|
||||
it "contains states for tiles that are visible on screen", ->
|
||||
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 3)).toBeUndefined()
|
||||
|
||||
line4 = editor.tokenizedLineForScreenRow(4)
|
||||
expectValues lineStateForScreenRow(presenter, 4), {
|
||||
screenRow: 4
|
||||
text: line4.text
|
||||
tags: line4.tags
|
||||
specialTokens: line4.specialTokens
|
||||
firstNonWhitespaceIndex: line4.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line4.firstTrailingWhitespaceIndex
|
||||
invisibles: line4.invisibles
|
||||
top: 10 * 4
|
||||
expectValues presenter.getState().content.tiles[0], {
|
||||
top: 0
|
||||
}
|
||||
expectValues presenter.getState().content.tiles[2], {
|
||||
top: 2
|
||||
}
|
||||
expectValues presenter.getState().content.tiles[4], {
|
||||
top: 4
|
||||
}
|
||||
expectValues presenter.getState().content.tiles[6], {
|
||||
top: 6
|
||||
}
|
||||
|
||||
line5 = editor.tokenizedLineForScreenRow(5)
|
||||
expectValues lineStateForScreenRow(presenter, 5), {
|
||||
screenRow: 5
|
||||
text: line5.text
|
||||
tags: line5.tags
|
||||
specialTokens: line5.specialTokens
|
||||
firstNonWhitespaceIndex: line5.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line5.firstTrailingWhitespaceIndex
|
||||
invisibles: line5.invisibles
|
||||
top: 10 * 5
|
||||
expect(presenter.getState().content.tiles[8]).toBeUndefined()
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(3)
|
||||
|
||||
expect(presenter.getState().content.tiles[0]).toBeUndefined()
|
||||
|
||||
expectValues presenter.getState().content.tiles[2], {
|
||||
top: -1
|
||||
}
|
||||
expectValues presenter.getState().content.tiles[4], {
|
||||
top: 1
|
||||
}
|
||||
expectValues presenter.getState().content.tiles[6], {
|
||||
top: 3
|
||||
}
|
||||
expectValues presenter.getState().content.tiles[8], {
|
||||
top: 5
|
||||
}
|
||||
expectValues presenter.getState().content.tiles[10], {
|
||||
top: 7
|
||||
}
|
||||
|
||||
line6 = editor.tokenizedLineForScreenRow(6)
|
||||
expectValues lineStateForScreenRow(presenter, 6), {
|
||||
screenRow: 6
|
||||
text: line6.text
|
||||
tags: line6.tags
|
||||
specialTokens: line6.specialTokens
|
||||
firstNonWhitespaceIndex: line6.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line6.firstTrailingWhitespaceIndex
|
||||
invisibles: line6.invisibles
|
||||
top: 10 * 6
|
||||
}
|
||||
expect(presenter.getState().content.tiles[12]).toBeUndefined()
|
||||
|
||||
line7 = editor.tokenizedLineForScreenRow(7)
|
||||
expectValues lineStateForScreenRow(presenter, 7), {
|
||||
screenRow: 7
|
||||
text: line7.text
|
||||
tags: line7.tags
|
||||
specialTokens: line7.specialTokens
|
||||
firstNonWhitespaceIndex: line7.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line7.firstTrailingWhitespaceIndex
|
||||
invisibles: line7.invisibles
|
||||
top: 10 * 7
|
||||
}
|
||||
|
||||
line8 = editor.tokenizedLineForScreenRow(8)
|
||||
expectValues lineStateForScreenRow(presenter, 8), {
|
||||
screenRow: 8
|
||||
text: line8.text
|
||||
tags: line8.tags
|
||||
specialTokens: line8.specialTokens
|
||||
firstNonWhitespaceIndex: line8.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line8.firstTrailingWhitespaceIndex
|
||||
invisibles: line8.invisibles
|
||||
top: 10 * 8
|
||||
}
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 9)).toBeUndefined()
|
||||
|
||||
it "does not overdraw above the first row", ->
|
||||
presenter = buildPresenter(explicitHeight: 15, scrollTop: 10, lineHeight: 10, lineOverdrawMargin: 2)
|
||||
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 1)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 2)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 3)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 4)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 5)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 6)).toBeUndefined()
|
||||
|
||||
it "does not overdraw below the last row", ->
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 105, lineHeight: 10, lineOverdrawMargin: 2)
|
||||
expect(lineStateForScreenRow(presenter, 7)).toBeUndefined()
|
||||
expect(lineStateForScreenRow(presenter, 8)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 9)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 10)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 11)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 12)).toBeDefined()
|
||||
|
||||
it "includes state for all lines if no external ::explicitHeight is assigned", ->
|
||||
presenter = buildPresenter(explicitHeight: null)
|
||||
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 12)).toBeDefined()
|
||||
it "includes state for all tiles if no external ::explicitHeight is assigned", ->
|
||||
presenter = buildPresenter(explicitHeight: null, tileSize: 2)
|
||||
expect(presenter.getState().content.tiles[0]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[12]).toBeDefined()
|
||||
|
||||
it "is empty until all of the required measurements are assigned", ->
|
||||
presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null)
|
||||
expect(presenter.getState().content.lines).toEqual({})
|
||||
expect(presenter.getState().content.tiles).toEqual({})
|
||||
|
||||
presenter.setExplicitHeight(25)
|
||||
expect(presenter.getState().content.lines).toEqual({})
|
||||
expect(presenter.getState().content.tiles).toEqual({})
|
||||
|
||||
presenter.setLineHeight(10)
|
||||
expect(presenter.getState().content.lines).toEqual({})
|
||||
expect(presenter.getState().content.tiles).toEqual({})
|
||||
|
||||
presenter.setScrollTop(0)
|
||||
expect(presenter.getState().content.lines).not.toEqual({})
|
||||
expect(presenter.getState().content.tiles).not.toEqual({})
|
||||
|
||||
it "updates when ::scrollTop changes", ->
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1)
|
||||
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 4)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 5)).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[0]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[2]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[4]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[6]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[8]).toBeUndefined()
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(25)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(2)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 0)).toBeUndefined()
|
||||
expect(lineStateForScreenRow(presenter, 1)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 6)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 7)).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[0]).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[2]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[4]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[6]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[8]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[10]).toBeUndefined()
|
||||
|
||||
it "updates when ::explicitHeight changes", ->
|
||||
presenter = buildPresenter(explicitHeight: 15, scrollTop: 15, lineHeight: 10, lineOverdrawMargin: 1)
|
||||
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
|
||||
|
||||
line5 = editor.tokenizedLineForScreenRow(5)
|
||||
expect(presenter.getState().content.tiles[0]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[2]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[4]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[6]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[8]).toBeUndefined()
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 4)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 5)).toBeUndefined()
|
||||
expectStateUpdate presenter, -> presenter.setExplicitHeight(8)
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setExplicitHeight(35)
|
||||
expect(presenter.getState().content.tiles[0]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[2]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[4]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[6]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[8]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[10]).toBeUndefined()
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 5)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 6)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 7)).toBeUndefined()
|
||||
|
||||
it "updates when ::lineHeight changes", ->
|
||||
presenter = buildPresenter(explicitHeight: 15, scrollTop: 10, lineHeight: 10, lineOverdrawMargin: 0)
|
||||
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 0)).toBeUndefined()
|
||||
expect(lineStateForScreenRow(presenter, 1)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 2)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 4)).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[0]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[2]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[4]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[6]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[8]).toBeUndefined()
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setLineHeight(5)
|
||||
expectStateUpdate presenter, -> presenter.setLineHeight(2)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 0)).toBeUndefined()
|
||||
expect(lineStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expect(lineStateForScreenRow(presenter, 2)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 5)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 6)).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[0]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[2]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[4]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[6]).toBeUndefined()
|
||||
|
||||
it "updates when the editor's content changes", ->
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10, tileSize: 2)
|
||||
|
||||
expectStateUpdate presenter, -> buffer.insert([2, 0], "hello\nworld\n")
|
||||
|
||||
@@ -832,53 +796,125 @@ describe "TextEditorPresenter", ->
|
||||
tags: line3.tags
|
||||
}
|
||||
|
||||
it "does not remove out-of-view lines corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", ->
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200)
|
||||
it "does not remove out-of-view tiles corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", ->
|
||||
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2, stoppedScrollingDelay: 200)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 4)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 5)).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[0]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[6]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[8]).toBeUndefined()
|
||||
|
||||
presenter.setMouseWheelScreenRow(0)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(35)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(4)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 0)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expect(lineStateForScreenRow(presenter, 7)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[0]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[2]).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[4]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[12]).toBeUndefined()
|
||||
|
||||
expectStateUpdate presenter, -> advanceClock(200)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 0)).toBeUndefined()
|
||||
expect(lineStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expect(lineStateForScreenRow(presenter, 2)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 7)).toBeDefined()
|
||||
expect(lineStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[0]).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[2]).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[4]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[12]).toBeUndefined()
|
||||
|
||||
|
||||
# should clear ::mouseWheelScreenRow after stoppedScrollingDelay elapses even if we don't scroll first
|
||||
presenter.setMouseWheelScreenRow(2)
|
||||
presenter.setMouseWheelScreenRow(4)
|
||||
advanceClock(200)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(45)
|
||||
expect(lineStateForScreenRow(presenter, 2)).toBeUndefined()
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(6)
|
||||
expect(presenter.getState().content.tiles[4]).toBeUndefined()
|
||||
|
||||
it "does not preserve on-screen lines even if they correspond to ::mouseWheelScreenRow", ->
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200)
|
||||
oldLine3 = editor.tokenizedLineForScreenRow(6)
|
||||
it "does not preserve deleted on-screen tiles even if they correspond to ::mouseWheelScreenRow", ->
|
||||
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2, stoppedScrollingDelay: 200)
|
||||
|
||||
presenter.setMouseWheelScreenRow(3)
|
||||
presenter.setMouseWheelScreenRow(2)
|
||||
|
||||
expectStateUpdate presenter, -> editor.getBuffer().insert([3, Infinity], 'xyz')
|
||||
newLine3 = editor.tokenizedLineForScreenRow(3)
|
||||
expectStateUpdate presenter, -> editor.setText("")
|
||||
|
||||
expect(presenter.getState().content.lines[oldLine3.id]).toBeUndefined()
|
||||
expect(presenter.getState().content.lines[newLine3.id]).toBeDefined()
|
||||
expect(presenter.getState().content.tiles[2]).toBeUndefined()
|
||||
expect(presenter.getState().content.tiles[0]).toBeDefined()
|
||||
|
||||
it "does not attempt to preserve lines corresponding to ::mouseWheelScreenRow if they have been deleted", ->
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200)
|
||||
presenter.setMouseWheelScreenRow(10)
|
||||
editor.setText('')
|
||||
describe "[tileId].lines[lineId]", -> # line state objects
|
||||
it "includes the state for visible lines in a tile", ->
|
||||
presenter = buildPresenter(explicitHeight: 3, scrollTop: 4, lineHeight: 1, tileSize: 3, stoppedScrollingDelay: 200)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 2)).toBeUndefined()
|
||||
|
||||
line3 = editor.tokenizedLineForScreenRow(3)
|
||||
expectValues lineStateForScreenRow(presenter, 3), {
|
||||
screenRow: 3
|
||||
text: line3.text
|
||||
tags: line3.tags
|
||||
specialTokens: line3.specialTokens
|
||||
firstNonWhitespaceIndex: line3.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line3.firstTrailingWhitespaceIndex
|
||||
invisibles: line3.invisibles
|
||||
top: 0
|
||||
}
|
||||
|
||||
line4 = editor.tokenizedLineForScreenRow(4)
|
||||
expectValues lineStateForScreenRow(presenter, 4), {
|
||||
screenRow: 4
|
||||
text: line4.text
|
||||
tags: line4.tags
|
||||
specialTokens: line4.specialTokens
|
||||
firstNonWhitespaceIndex: line4.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line4.firstTrailingWhitespaceIndex
|
||||
invisibles: line4.invisibles
|
||||
top: 1
|
||||
}
|
||||
|
||||
line5 = editor.tokenizedLineForScreenRow(5)
|
||||
expectValues lineStateForScreenRow(presenter, 5), {
|
||||
screenRow: 5
|
||||
text: line5.text
|
||||
tags: line5.tags
|
||||
specialTokens: line5.specialTokens
|
||||
firstNonWhitespaceIndex: line5.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line5.firstTrailingWhitespaceIndex
|
||||
invisibles: line5.invisibles
|
||||
top: 2
|
||||
}
|
||||
|
||||
line6 = editor.tokenizedLineForScreenRow(6)
|
||||
expectValues lineStateForScreenRow(presenter, 6), {
|
||||
screenRow: 6
|
||||
text: line6.text
|
||||
tags: line6.tags
|
||||
specialTokens: line6.specialTokens
|
||||
firstNonWhitespaceIndex: line6.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line6.firstTrailingWhitespaceIndex
|
||||
invisibles: line6.invisibles
|
||||
top: 0
|
||||
}
|
||||
|
||||
line7 = editor.tokenizedLineForScreenRow(7)
|
||||
expectValues lineStateForScreenRow(presenter, 7), {
|
||||
screenRow: 7
|
||||
text: line7.text
|
||||
tags: line7.tags
|
||||
specialTokens: line7.specialTokens
|
||||
firstNonWhitespaceIndex: line7.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line7.firstTrailingWhitespaceIndex
|
||||
invisibles: line7.invisibles
|
||||
top: 1
|
||||
}
|
||||
|
||||
line8 = editor.tokenizedLineForScreenRow(8)
|
||||
expectValues lineStateForScreenRow(presenter, 8), {
|
||||
screenRow: 8
|
||||
text: line8.text
|
||||
tags: line8.tags
|
||||
specialTokens: line8.specialTokens
|
||||
firstNonWhitespaceIndex: line8.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line8.firstTrailingWhitespaceIndex
|
||||
invisibles: line8.invisibles
|
||||
top: 2
|
||||
}
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 9)).toBeUndefined()
|
||||
|
||||
describe "[lineId]", -> # line state objects
|
||||
it "includes the .endOfLineInvisibles if the editor.showInvisibles config option is true", ->
|
||||
editor.setText("hello\nworld\r\n")
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10)
|
||||
@@ -1031,9 +1067,9 @@ describe "TextEditorPresenter", ->
|
||||
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
|
||||
|
||||
expect(stateForCursor(presenter, 0)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 2)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10 - 20, left: 12 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 4)).toBeUndefined()
|
||||
|
||||
it "is empty until all of the required measurements are assigned", ->
|
||||
@@ -1069,8 +1105,21 @@ describe "TextEditorPresenter", ->
|
||||
expect(stateForCursor(presenter, 0)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 1)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 2)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 0, left: 12 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10 - 50, left: 4 * 10, width: 10, height: 10}
|
||||
|
||||
it "updates when ::scrollTop changes after the model was changed", ->
|
||||
editor.setCursorBufferPosition([8, 22])
|
||||
presenter = buildPresenter(explicitHeight: 50, scrollTop: 10 * 8)
|
||||
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 10 * 22, width: 10, height: 10}
|
||||
|
||||
expectStateUpdate presenter, ->
|
||||
editor.getBuffer().deleteRow(12)
|
||||
editor.getBuffer().deleteRow(11)
|
||||
editor.getBuffer().deleteRow(10)
|
||||
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 20, left: 10 * 22, width: 10, height: 10}
|
||||
|
||||
it "updates when ::explicitHeight changes", ->
|
||||
editor.setSelectedBufferRanges([
|
||||
@@ -1084,9 +1133,9 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setExplicitHeight(30)
|
||||
expect(stateForCursor(presenter, 0)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 2)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10 - 20, left: 12 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 4)).toBeUndefined()
|
||||
|
||||
it "updates when ::lineHeight changes", ->
|
||||
@@ -1103,15 +1152,15 @@ describe "TextEditorPresenter", ->
|
||||
expect(stateForCursor(presenter, 0)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 1)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 2)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 5, left: 12 * 10, width: 10, height: 5}
|
||||
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 5, left: 4 * 10, width: 10, height: 5}
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 5, left: 12 * 10, width: 10, height: 5}
|
||||
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 5 - 20, left: 4 * 10, width: 10, height: 5}
|
||||
|
||||
it "updates when ::baseCharacterWidth changes", ->
|
||||
editor.setCursorBufferPosition([2, 4])
|
||||
presenter = buildPresenter(explicitHeight: 20, scrollTop: 20)
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20)
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 2 * 10, left: 4 * 20, width: 20, height: 10}
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 4 * 20, width: 20, height: 10}
|
||||
|
||||
it "updates when scoped character widths change", ->
|
||||
waitsForPromise ->
|
||||
@@ -1137,11 +1186,11 @@ describe "TextEditorPresenter", ->
|
||||
# moving into view
|
||||
expect(stateForCursor(presenter, 0)).toBeUndefined()
|
||||
editor.getCursors()[0].setBufferPosition([2, 4])
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 4 * 10, width: 10, height: 10}
|
||||
|
||||
# showing
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].clear()
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 3 * 10, left: 5 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 5, left: 5 * 10, width: 10, height: 10}
|
||||
|
||||
# hiding
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 5]])
|
||||
@@ -1153,11 +1202,11 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
# adding
|
||||
expectStateUpdate presenter, -> editor.addCursorAtBufferPosition([4, 4])
|
||||
expect(stateForCursor(presenter, 2)).toEqual {top: 4 * 10, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 2)).toEqual {top: 5, left: 4 * 10, width: 10, height: 10}
|
||||
|
||||
# moving added cursor
|
||||
expectStateUpdate presenter, -> editor.getCursors()[2].setBufferPosition([4, 6])
|
||||
expect(stateForCursor(presenter, 2)).toEqual {top: 4 * 10, left: 6 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 2)).toEqual {top: 5, left: 6 * 10, width: 10, height: 10}
|
||||
|
||||
# destroying
|
||||
destroyedCursor = editor.getCursors()[2]
|
||||
@@ -1275,39 +1324,39 @@ describe "TextEditorPresenter", ->
|
||||
expectValues stateForHighlight(presenter, highlight2), {
|
||||
class: 'b'
|
||||
regions: [
|
||||
{top: 2 * 10, left: 0 * 10, width: 6 * 10, height: 1 * 10}
|
||||
{top: 2 * 10 - 20, left: 0 * 10, width: 6 * 10, height: 1 * 10}
|
||||
]
|
||||
}
|
||||
|
||||
expectValues stateForHighlight(presenter, highlight3), {
|
||||
class: 'c'
|
||||
regions: [
|
||||
{top: 2 * 10, left: 0 * 10, right: 0, height: 1 * 10}
|
||||
{top: 3 * 10, left: 0 * 10, width: 6 * 10, height: 1 * 10}
|
||||
{top: 2 * 10 - 20, left: 0 * 10, right: 0, height: 1 * 10}
|
||||
{top: 3 * 10 - 20, left: 0 * 10, width: 6 * 10, height: 1 * 10}
|
||||
]
|
||||
}
|
||||
|
||||
expectValues stateForHighlight(presenter, highlight4), {
|
||||
class: 'd'
|
||||
regions: [
|
||||
{top: 2 * 10, left: 6 * 10, right: 0, height: 1 * 10}
|
||||
{top: 3 * 10, left: 0, right: 0, height: 1 * 10}
|
||||
{top: 4 * 10, left: 0, width: 6 * 10, height: 1 * 10}
|
||||
{top: 2 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10}
|
||||
{top: 3 * 10 - 20, left: 0, right: 0, height: 1 * 10}
|
||||
{top: 4 * 10 - 20, left: 0, width: 6 * 10, height: 1 * 10}
|
||||
]
|
||||
}
|
||||
|
||||
expectValues stateForHighlight(presenter, highlight5), {
|
||||
class: 'e'
|
||||
regions: [
|
||||
{top: 3 * 10, left: 6 * 10, right: 0, height: 1 * 10}
|
||||
{top: 4 * 10, left: 0 * 10, right: 0, height: 2 * 10}
|
||||
{top: 3 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10}
|
||||
{top: 4 * 10 - 20, left: 0 * 10, right: 0, height: 2 * 10}
|
||||
]
|
||||
}
|
||||
|
||||
expectValues stateForHighlight(presenter, highlight6), {
|
||||
class: 'f'
|
||||
regions: [
|
||||
{top: 5 * 10, left: 6 * 10, right: 0, height: 1 * 10}
|
||||
{top: 5 * 10 - 20, left: 6 * 10, right: 0, height: 1 * 10}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1913,20 +1962,18 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
getLineNumberGutterState(presenter).content.lineNumbers[key]
|
||||
|
||||
it "contains states for line numbers that are visible on screen, plus and minus the overdraw margin", ->
|
||||
it "contains states for line numbers that are visible on screen", ->
|
||||
editor.foldBufferRow(4)
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setEditorWidthInChars(50)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, lineOverdrawMargin: 1)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 2), {screenRow: 2, bufferRow: 2, softWrapped: false, top: 2 * 10}
|
||||
expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 3), {screenRow: 3, bufferRow: 3, softWrapped: false, top: 3 * 10}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 4), {screenRow: 4, bufferRow: 3, softWrapped: true, top: 4 * 10}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false, top: 5 * 10}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 7, softWrapped: false, top: 6 * 10}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 8, softWrapped: false, top: 7 * 10}
|
||||
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
|
||||
|
||||
it "includes states for all line numbers if no ::explicitHeight is assigned", ->
|
||||
presenter = buildPresenter(explicitHeight: null)
|
||||
@@ -1937,43 +1984,43 @@ describe "TextEditorPresenter", ->
|
||||
editor.foldBufferRow(4)
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setEditorWidthInChars(50)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineOverdrawMargin: 1)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8}
|
||||
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7}
|
||||
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(20)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 1), {bufferRow: 1}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7}
|
||||
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 5), {bufferRow: 4}
|
||||
expect(lineNumberStateForScreenRow(presenter, 6)).toBeUndefined()
|
||||
|
||||
it "updates when ::explicitHeight changes", ->
|
||||
editor.foldBufferRow(4)
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setEditorWidthInChars(50)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineOverdrawMargin: 1)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8}
|
||||
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7}
|
||||
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
|
||||
|
||||
expectStateUpdate presenter, -> presenter.setExplicitHeight(35)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 8), {bufferRow: 8}
|
||||
expect(lineNumberStateForScreenRow(presenter, 9)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8}
|
||||
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
|
||||
it "updates when ::lineHeight changes", ->
|
||||
editor.foldBufferRow(4)
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setEditorWidthInChars(50)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineOverdrawMargin: 0)
|
||||
presenter = buildPresenter(explicitHeight: 25, scrollTop: 0)
|
||||
|
||||
expectValues lineNumberStateForScreenRow(presenter, 0), {bufferRow: 0}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
|
||||
@@ -1989,7 +2036,7 @@ describe "TextEditorPresenter", ->
|
||||
editor.foldBufferRow(4)
|
||||
editor.setSoftWrapped(true)
|
||||
editor.setEditorWidthInChars(50)
|
||||
presenter = buildPresenter(explicitHeight: 35, scrollTop: 30, lineOverdrawMargin: 0)
|
||||
presenter = buildPresenter(explicitHeight: 35, scrollTop: 30)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined()
|
||||
expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3}
|
||||
@@ -2011,26 +2058,26 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
|
||||
it "does not remove out-of-view line numbers corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", ->
|
||||
presenter = buildPresenter(explicitHeight: 25, lineOverdrawMargin: 1, stoppedScrollingDelay: 200)
|
||||
presenter = buildPresenter(explicitHeight: 25, stoppedScrollingDelay: 200)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 0)).toBeDefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4)).toBeDefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3)).toBeDefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4)).toBeUndefined()
|
||||
|
||||
presenter.setMouseWheelScreenRow(0)
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(35)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 0)).toBeDefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 7)).toBeDefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6)).toBeDefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
|
||||
|
||||
expectStateUpdate presenter, -> advanceClock(200)
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 7)).toBeDefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6)).toBeDefined()
|
||||
expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined()
|
||||
|
||||
it "correctly handles the first screen line being soft-wrapped", ->
|
||||
editor.setSoftWrapped(true)
|
||||
@@ -2213,12 +2260,11 @@ describe "TextEditorPresenter", ->
|
||||
scrollTop = 0
|
||||
lineHeight = 10
|
||||
explicitHeight = lineHeight * 10
|
||||
lineOverdrawMargin = 1
|
||||
|
||||
beforeEach ->
|
||||
# At the beginning of each test, decoration1 and decoration2 are in visible range,
|
||||
# but not decoration3.
|
||||
presenter = buildPresenter({explicitHeight, scrollTop, lineHeight, lineOverdrawMargin})
|
||||
presenter = buildPresenter({explicitHeight, scrollTop, lineHeight})
|
||||
gutter = editor.addGutter({name: 'test-gutter', visible: true})
|
||||
decorationItem = document.createElement('div')
|
||||
decorationItem.class = 'decoration-item'
|
||||
@@ -2589,7 +2635,7 @@ describe "TextEditorPresenter", ->
|
||||
editor.setEditorWidthInChars(80)
|
||||
presenterParams =
|
||||
model: editor
|
||||
lineOverdrawMargin: 1
|
||||
|
||||
presenter = new TextEditorPresenter(presenterParams)
|
||||
statements = []
|
||||
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
_ = require 'underscore-plus'
|
||||
{toArray} = require 'underscore-plus'
|
||||
{$$} = require 'space-pen'
|
||||
|
||||
CursorsComponent = require './cursors-component'
|
||||
HighlightsComponent = require './highlights-component'
|
||||
TokenIterator = require './token-iterator'
|
||||
TileComponent = require './tile-component'
|
||||
|
||||
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
|
||||
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
|
||||
WrapperDiv = document.createElement('div')
|
||||
TokenTextEscapeRegex = /[&"'<>]/g
|
||||
MaxTokenLength = 20000
|
||||
|
||||
cloneObject = (object) ->
|
||||
clone = {}
|
||||
@@ -22,12 +16,7 @@ class LinesComponent
|
||||
placeholderTextDiv: null
|
||||
|
||||
constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) ->
|
||||
@tokenIterator = new TokenIterator
|
||||
@measuredLines = new Set
|
||||
@lineNodesByLineId = {}
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@renderedDecorationsByLineId = {}
|
||||
@tileComponentsByTileId = {}
|
||||
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.classList.add('lines')
|
||||
@@ -48,17 +37,12 @@ class LinesComponent
|
||||
|
||||
updateSync: (state) ->
|
||||
@newState = state.content
|
||||
@oldState ?= {lines: {}}
|
||||
@oldState ?= {tiles: {}}
|
||||
|
||||
if @newState.scrollHeight isnt @oldState.scrollHeight
|
||||
@domNode.style.height = @newState.scrollHeight + 'px'
|
||||
@oldState.scrollHeight = @newState.scrollHeight
|
||||
|
||||
if @newState.scrollTop isnt @oldState.scrollTop or @newState.scrollLeft isnt @oldState.scrollLeft
|
||||
@domNode.style['-webkit-transform'] = "translate3d(#{-@newState.scrollLeft}px, #{-@newState.scrollTop}px, 0px)"
|
||||
@oldState.scrollTop = @newState.scrollTop
|
||||
@oldState.scrollLeft = @newState.scrollLeft
|
||||
|
||||
if @newState.backgroundColor isnt @oldState.backgroundColor
|
||||
@domNode.style.backgroundColor = @newState.backgroundColor
|
||||
@oldState.backgroundColor = @newState.backgroundColor
|
||||
@@ -71,8 +55,8 @@ class LinesComponent
|
||||
@placeholderTextDiv.textContent = @newState.placeholderText
|
||||
@domNode.appendChild(@placeholderTextDiv)
|
||||
|
||||
@removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
|
||||
@updateLineNodes()
|
||||
@removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
|
||||
@updateTileNodes()
|
||||
|
||||
if @newState.scrollWidth isnt @oldState.scrollWidth
|
||||
@domNode.style.width = @newState.scrollWidth + 'px'
|
||||
@@ -84,248 +68,35 @@ class LinesComponent
|
||||
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
|
||||
@oldState.scrollWidth = @newState.scrollWidth
|
||||
|
||||
removeLineNodes: ->
|
||||
@removeLineNode(id) for id of @oldState.lines
|
||||
removeTileNodes: ->
|
||||
@removeTileNode(id) for id of @oldState.tiles
|
||||
return
|
||||
|
||||
removeLineNode: (id) ->
|
||||
@lineNodesByLineId[id].remove()
|
||||
delete @lineNodesByLineId[id]
|
||||
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
|
||||
delete @screenRowsByLineId[id]
|
||||
delete @oldState.lines[id]
|
||||
removeTileNode: (id) ->
|
||||
node = @tileComponentsByTileId[id].getDomNode()
|
||||
|
||||
updateLineNodes: ->
|
||||
for id of @oldState.lines
|
||||
unless @newState.lines.hasOwnProperty(id)
|
||||
@removeLineNode(id)
|
||||
node.remove()
|
||||
delete @tileComponentsByTileId[id]
|
||||
delete @oldState.tiles[id]
|
||||
|
||||
newLineIds = null
|
||||
newLinesHTML = null
|
||||
updateTileNodes: ->
|
||||
for id of @oldState.tiles
|
||||
unless @newState.tiles.hasOwnProperty(id)
|
||||
@removeTileNode(id)
|
||||
|
||||
for id, lineState of @newState.lines
|
||||
if @oldState.lines.hasOwnProperty(id)
|
||||
@updateLineNode(id)
|
||||
for id, tileState of @newState.tiles
|
||||
if @oldState.tiles.hasOwnProperty(id)
|
||||
tileComponent = @tileComponentsByTileId[id]
|
||||
else
|
||||
newLineIds ?= []
|
||||
newLinesHTML ?= ""
|
||||
newLineIds.push(id)
|
||||
newLinesHTML += @buildLineHTML(id)
|
||||
@screenRowsByLineId[id] = lineState.screenRow
|
||||
@lineIdsByScreenRow[lineState.screenRow] = id
|
||||
@oldState.lines[id] = cloneObject(lineState)
|
||||
tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter})
|
||||
|
||||
return unless newLineIds?
|
||||
@domNode.appendChild(tileComponent.getDomNode())
|
||||
@oldState.tiles[id] = cloneObject(tileState)
|
||||
|
||||
WrapperDiv.innerHTML = newLinesHTML
|
||||
newLineNodes = _.toArray(WrapperDiv.children)
|
||||
for id, i in newLineIds
|
||||
lineNode = newLineNodes[i]
|
||||
@lineNodesByLineId[id] = lineNode
|
||||
@domNode.appendChild(lineNode)
|
||||
tileComponent.updateSync(@newState)
|
||||
|
||||
return
|
||||
|
||||
buildLineHTML: (id) ->
|
||||
{scrollWidth} = @newState
|
||||
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id]
|
||||
|
||||
classes = ''
|
||||
if decorationClasses?
|
||||
for decorationClass in decorationClasses
|
||||
classes += decorationClass + ' '
|
||||
classes += 'line'
|
||||
|
||||
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{scrollWidth}px;\" data-screen-row=\"#{screenRow}\">"
|
||||
|
||||
if text is ""
|
||||
lineHTML += @buildEmptyLineInnerHTML(id)
|
||||
else
|
||||
lineHTML += @buildLineInnerHTML(id)
|
||||
|
||||
lineHTML += '<span class="fold-marker"></span>' if fold
|
||||
lineHTML += "</div>"
|
||||
lineHTML
|
||||
|
||||
buildEmptyLineInnerHTML: (id) ->
|
||||
{indentGuidesVisible} = @newState
|
||||
{indentLevel, tabLength, endOfLineInvisibles} = @newState.lines[id]
|
||||
|
||||
if indentGuidesVisible and indentLevel > 0
|
||||
invisibleIndex = 0
|
||||
lineHTML = ''
|
||||
for i in [0...indentLevel]
|
||||
lineHTML += "<span class='indent-guide'>"
|
||||
for j in [0...tabLength]
|
||||
if invisible = endOfLineInvisibles?[invisibleIndex++]
|
||||
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
|
||||
else
|
||||
lineHTML += ' '
|
||||
lineHTML += "</span>"
|
||||
|
||||
while invisibleIndex < endOfLineInvisibles?.length
|
||||
lineHTML += "<span class='invisible-character'>#{endOfLineInvisibles[invisibleIndex++]}</span>"
|
||||
|
||||
lineHTML
|
||||
else
|
||||
@buildEndOfLineHTML(id) or ' '
|
||||
|
||||
buildLineInnerHTML: (id) ->
|
||||
lineState = @newState.lines[id]
|
||||
{firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, invisibles} = lineState
|
||||
lineIsWhitespaceOnly = firstTrailingWhitespaceIndex is 0
|
||||
|
||||
innerHTML = ""
|
||||
@tokenIterator.reset(lineState)
|
||||
|
||||
while @tokenIterator.next()
|
||||
for scope in @tokenIterator.getScopeEnds()
|
||||
innerHTML += "</span>"
|
||||
|
||||
for scope in @tokenIterator.getScopeStarts()
|
||||
innerHTML += "<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
|
||||
|
||||
tokenStart = @tokenIterator.getScreenStart()
|
||||
tokenEnd = @tokenIterator.getScreenEnd()
|
||||
tokenText = @tokenIterator.getText()
|
||||
isHardTab = @tokenIterator.isHardTab()
|
||||
|
||||
if hasLeadingWhitespace = tokenStart < firstNonWhitespaceIndex
|
||||
tokenFirstNonWhitespaceIndex = firstNonWhitespaceIndex - tokenStart
|
||||
else
|
||||
tokenFirstNonWhitespaceIndex = null
|
||||
|
||||
if hasTrailingWhitespace = tokenEnd > firstTrailingWhitespaceIndex
|
||||
tokenFirstTrailingWhitespaceIndex = Math.max(0, firstTrailingWhitespaceIndex - tokenStart)
|
||||
else
|
||||
tokenFirstTrailingWhitespaceIndex = null
|
||||
|
||||
hasIndentGuide =
|
||||
@newState.indentGuidesVisible and
|
||||
(hasLeadingWhitespace or lineIsWhitespaceOnly)
|
||||
|
||||
hasInvisibleCharacters =
|
||||
(invisibles?.tab and isHardTab) or
|
||||
(invisibles?.space and (hasLeadingWhitespace or hasTrailingWhitespace))
|
||||
|
||||
innerHTML += @buildTokenHTML(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters)
|
||||
|
||||
for scope in @tokenIterator.getScopeEnds()
|
||||
innerHTML += "</span>"
|
||||
|
||||
for scope in @tokenIterator.getScopes()
|
||||
innerHTML += "</span>"
|
||||
|
||||
innerHTML += @buildEndOfLineHTML(id)
|
||||
innerHTML
|
||||
|
||||
buildTokenHTML: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
|
||||
if isHardTab
|
||||
classes = 'hard-tab'
|
||||
classes += ' leading-whitespace' if firstNonWhitespaceIndex?
|
||||
classes += ' trailing-whitespace' if firstTrailingWhitespaceIndex?
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if hasInvisibleCharacters
|
||||
return "<span class='#{classes}'>#{@escapeTokenText(tokenText)}</span>"
|
||||
else
|
||||
startIndex = 0
|
||||
endIndex = tokenText.length
|
||||
|
||||
leadingHtml = ''
|
||||
trailingHtml = ''
|
||||
|
||||
if firstNonWhitespaceIndex?
|
||||
leadingWhitespace = tokenText.substring(0, firstNonWhitespaceIndex)
|
||||
|
||||
classes = 'leading-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if hasInvisibleCharacters
|
||||
|
||||
leadingHtml = "<span class='#{classes}'>#{leadingWhitespace}</span>"
|
||||
startIndex = firstNonWhitespaceIndex
|
||||
|
||||
if firstTrailingWhitespaceIndex?
|
||||
tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
|
||||
trailingWhitespace = tokenText.substring(firstTrailingWhitespaceIndex)
|
||||
|
||||
classes = 'trailing-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
|
||||
classes += ' invisible-character' if hasInvisibleCharacters
|
||||
|
||||
trailingHtml = "<span class='#{classes}'>#{trailingWhitespace}</span>"
|
||||
|
||||
endIndex = firstTrailingWhitespaceIndex
|
||||
|
||||
html = leadingHtml
|
||||
if tokenText.length > MaxTokenLength
|
||||
while startIndex < endIndex
|
||||
html += "<span>" + @escapeTokenText(tokenText, startIndex, startIndex + MaxTokenLength) + "</span>"
|
||||
startIndex += MaxTokenLength
|
||||
else
|
||||
html += @escapeTokenText(tokenText, startIndex, endIndex)
|
||||
|
||||
html += trailingHtml
|
||||
html
|
||||
|
||||
escapeTokenText: (tokenText, startIndex, endIndex) ->
|
||||
if startIndex? and endIndex? and startIndex > 0 or endIndex < tokenText.length
|
||||
tokenText = tokenText.slice(startIndex, endIndex)
|
||||
tokenText.replace(TokenTextEscapeRegex, @escapeTokenTextReplace)
|
||||
|
||||
escapeTokenTextReplace: (match) ->
|
||||
switch match
|
||||
when '&' then '&'
|
||||
when '"' then '"'
|
||||
when "'" then '''
|
||||
when '<' then '<'
|
||||
when '>' then '>'
|
||||
else match
|
||||
|
||||
buildEndOfLineHTML: (id) ->
|
||||
{endOfLineInvisibles} = @newState.lines[id]
|
||||
|
||||
html = ''
|
||||
if endOfLineInvisibles?
|
||||
for invisible in endOfLineInvisibles
|
||||
html += "<span class='invisible-character'>#{invisible}</span>"
|
||||
html
|
||||
|
||||
updateLineNode: (id) ->
|
||||
oldLineState = @oldState.lines[id]
|
||||
newLineState = @newState.lines[id]
|
||||
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
|
||||
if @newState.scrollWidth isnt @oldState.scrollWidth
|
||||
lineNode.style.width = @newState.scrollWidth + 'px'
|
||||
|
||||
newDecorationClasses = newLineState.decorationClasses
|
||||
oldDecorationClasses = oldLineState.decorationClasses
|
||||
|
||||
if oldDecorationClasses?
|
||||
for decorationClass in oldDecorationClasses
|
||||
unless newDecorationClasses? and decorationClass in newDecorationClasses
|
||||
lineNode.classList.remove(decorationClass)
|
||||
|
||||
if newDecorationClasses?
|
||||
for decorationClass in newDecorationClasses
|
||||
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
|
||||
lineNode.classList.add(decorationClass)
|
||||
|
||||
oldLineState.decorationClasses = newLineState.decorationClasses
|
||||
|
||||
if newLineState.top isnt oldLineState.top
|
||||
lineNode.style.top = newLineState.top + 'px'
|
||||
oldLineState.top = newLineState.cop
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
lineNode.dataset.screenRow = newLineState.screenRow
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
@lineIdsByScreenRow[newLineState.screenRow] = id
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||
|
||||
measureLineHeightAndDefaultCharWidth: ->
|
||||
@domNode.appendChild(DummyLineNode)
|
||||
lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
|
||||
@@ -343,59 +114,18 @@ class LinesComponent
|
||||
|
||||
measureCharactersInNewLines: ->
|
||||
@presenter.batchCharacterMeasurement =>
|
||||
for id, lineState of @oldState.lines
|
||||
unless @measuredLines.has(id)
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
@measureCharactersInLine(id, lineState, lineNode)
|
||||
for id, component of @tileComponentsByTileId
|
||||
component.measureCharactersInNewLines()
|
||||
|
||||
return
|
||||
|
||||
measureCharactersInLine: (lineId, tokenizedLine, lineNode) ->
|
||||
rangeForMeasurement = null
|
||||
iterator = null
|
||||
charIndex = 0
|
||||
|
||||
@tokenIterator.reset(tokenizedLine)
|
||||
while @tokenIterator.next()
|
||||
scopes = @tokenIterator.getScopes()
|
||||
text = @tokenIterator.getText()
|
||||
charWidths = @presenter.getScopedCharacterWidths(scopes)
|
||||
|
||||
textIndex = 0
|
||||
while textIndex < text.length
|
||||
if @tokenIterator.isPairedCharacter()
|
||||
char = text
|
||||
charLength = 2
|
||||
textIndex += 2
|
||||
else
|
||||
char = text[textIndex]
|
||||
charLength = 1
|
||||
textIndex++
|
||||
|
||||
continue if char is '\0'
|
||||
|
||||
unless charWidths[char]?
|
||||
unless textNode?
|
||||
rangeForMeasurement ?= document.createRange()
|
||||
iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter)
|
||||
textNode = iterator.nextNode()
|
||||
textNodeIndex = 0
|
||||
nextTextNodeIndex = textNode.textContent.length
|
||||
|
||||
while nextTextNodeIndex <= charIndex
|
||||
textNode = iterator.nextNode()
|
||||
textNodeIndex = nextTextNodeIndex
|
||||
nextTextNodeIndex = textNodeIndex + textNode.textContent.length
|
||||
|
||||
i = charIndex - textNodeIndex
|
||||
rangeForMeasurement.setStart(textNode, i)
|
||||
rangeForMeasurement.setEnd(textNode, i + charLength)
|
||||
charWidth = rangeForMeasurement.getBoundingClientRect().width
|
||||
@presenter.setScopedCharacterWidth(scopes, char, charWidth)
|
||||
|
||||
charIndex += charLength
|
||||
|
||||
@measuredLines.add(lineId)
|
||||
|
||||
clearScopedCharWidths: ->
|
||||
@measuredLines.clear()
|
||||
for id, component of @tileComponentsByTileId
|
||||
component.clearMeasurements()
|
||||
|
||||
@presenter.clearScopedCharacterWidths()
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
tile = @presenter.tileForRow(screenRow)
|
||||
|
||||
@tileComponentsByTileId[tile]?.lineNodeForScreenRow(screenRow)
|
||||
|
||||
@@ -18,7 +18,7 @@ class TextEditorComponent
|
||||
scrollSensitivity: 0.4
|
||||
cursorBlinkPeriod: 800
|
||||
cursorBlinkResumeDelay: 100
|
||||
lineOverdrawMargin: 15
|
||||
tileSize: 12
|
||||
|
||||
pendingScrollTop: null
|
||||
pendingScrollLeft: null
|
||||
@@ -36,8 +36,8 @@ class TextEditorComponent
|
||||
gutterComponent: null
|
||||
mounted: true
|
||||
|
||||
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, lineOverdrawMargin}) ->
|
||||
@lineOverdrawMargin = lineOverdrawMargin if lineOverdrawMargin?
|
||||
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize}) ->
|
||||
@tileSize = tileSize if tileSize?
|
||||
@disposables = new CompositeDisposable
|
||||
|
||||
@observeConfig()
|
||||
@@ -47,7 +47,7 @@ class TextEditorComponent
|
||||
model: @editor
|
||||
scrollTop: @editor.getScrollTop()
|
||||
scrollLeft: @editor.getScrollLeft()
|
||||
lineOverdrawMargin: lineOverdrawMargin
|
||||
tileSize: tileSize
|
||||
cursorBlinkPeriod: @cursorBlinkPeriod
|
||||
cursorBlinkResumeDelay: @cursorBlinkResumeDelay
|
||||
stoppedScrollingDelay: 200
|
||||
@@ -768,8 +768,8 @@ class TextEditorComponent
|
||||
{clientX, clientY} = event
|
||||
|
||||
linesClientRect = @linesComponent.getDomNode().getBoundingClientRect()
|
||||
top = clientY - linesClientRect.top
|
||||
left = clientX - linesClientRect.left
|
||||
top = clientY - linesClientRect.top + @presenter.scrollTop
|
||||
left = clientX - linesClientRect.left + @presenter.scrollLeft
|
||||
{top, left}
|
||||
|
||||
getModel: ->
|
||||
|
||||
@@ -15,8 +15,9 @@ class TextEditorElement extends HTMLElement
|
||||
componentDescriptor: null
|
||||
component: null
|
||||
attached: false
|
||||
lineOverdrawMargin: null
|
||||
tileSize: null
|
||||
focusOnAttach: false
|
||||
hasTiledRendering: true
|
||||
|
||||
createdCallback: ->
|
||||
@emitter = new Emitter
|
||||
@@ -110,7 +111,7 @@ class TextEditorElement extends HTMLElement
|
||||
rootElement: @rootElement
|
||||
stylesElement: @stylesElement
|
||||
editor: @model
|
||||
lineOverdrawMargin: @lineOverdrawMargin
|
||||
tileSize: @tileSize
|
||||
useShadowDOM: @useShadowDOM
|
||||
)
|
||||
@rootElement.appendChild(@component.getDomNode())
|
||||
|
||||
@@ -15,11 +15,12 @@ class TextEditorPresenter
|
||||
constructor: (params) ->
|
||||
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
|
||||
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
|
||||
{@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params
|
||||
{@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params
|
||||
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
|
||||
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
|
||||
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
|
||||
@gutterWidth ?= 0
|
||||
@tileSize ?= 12
|
||||
|
||||
@disposables = new CompositeDisposable
|
||||
@emitter = new Emitter
|
||||
@@ -74,7 +75,7 @@ class TextEditorPresenter
|
||||
@updateHiddenInputState() if @shouldUpdateHiddenInputState
|
||||
@updateContentState() if @shouldUpdateContentState
|
||||
@updateDecorations() if @shouldUpdateDecorations
|
||||
@updateLinesState() if @shouldUpdateLinesState
|
||||
@updateTilesState() if @shouldUpdateTilesState
|
||||
@updateCursorsState() if @shouldUpdateCursorsState
|
||||
@updateOverlaysState() if @shouldUpdateOverlaysState
|
||||
@updateLineNumberGutterState() if @shouldUpdateLineNumberGutterState
|
||||
@@ -96,7 +97,7 @@ class TextEditorPresenter
|
||||
@shouldUpdateHiddenInputState = false
|
||||
@shouldUpdateContentState = false
|
||||
@shouldUpdateDecorations = false
|
||||
@shouldUpdateLinesState = false
|
||||
@shouldUpdateTilesState = false
|
||||
@shouldUpdateCursorsState = false
|
||||
@shouldUpdateOverlaysState = false
|
||||
@shouldUpdateLineNumberGutterState = false
|
||||
@@ -114,7 +115,8 @@ class TextEditorPresenter
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateTilesState = true
|
||||
@shouldUpdateLineNumberGutterState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@@ -130,7 +132,7 @@ class TextEditorPresenter
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateTilesState = true
|
||||
@shouldUpdateLineNumberGutterState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@@ -205,7 +207,7 @@ class TextEditorPresenter
|
||||
content:
|
||||
scrollingVertically: false
|
||||
cursorsVisible: false
|
||||
lines: {}
|
||||
tiles: {}
|
||||
highlights: {}
|
||||
overlays: {}
|
||||
gutters: []
|
||||
@@ -231,7 +233,7 @@ class TextEditorPresenter
|
||||
@updateHiddenInputState()
|
||||
@updateContentState()
|
||||
@updateDecorations()
|
||||
@updateLinesState()
|
||||
@updateTilesState()
|
||||
@updateCursorsState()
|
||||
@updateOverlaysState()
|
||||
@updateLineNumberGutterState()
|
||||
@@ -282,8 +284,6 @@ class TextEditorPresenter
|
||||
{top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange())
|
||||
|
||||
if @focused
|
||||
top -= @scrollTop
|
||||
left -= @scrollLeft
|
||||
@state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0)
|
||||
@state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0)
|
||||
else
|
||||
@@ -300,56 +300,84 @@ class TextEditorPresenter
|
||||
@state.content.backgroundColor = if @model.isMini() then null else @backgroundColor
|
||||
@state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null
|
||||
|
||||
updateLinesState: ->
|
||||
tileForRow: (row) ->
|
||||
row - (row % @tileSize)
|
||||
|
||||
getStartTileRow: ->
|
||||
Math.max(0, @tileForRow(@startRow))
|
||||
|
||||
getEndTileRow: ->
|
||||
Math.min(
|
||||
@tileForRow(@model.getScreenLineCount()), @tileForRow(@endRow)
|
||||
)
|
||||
|
||||
updateTilesState: ->
|
||||
return unless @startRow? and @endRow? and @lineHeight?
|
||||
|
||||
visibleTiles = {}
|
||||
for startRow in [@getStartTileRow()..@getEndTileRow()] by @tileSize
|
||||
endRow = Math.min(@model.getScreenLineCount(), startRow + @tileSize)
|
||||
|
||||
tile = @state.content.tiles[startRow] ?= {}
|
||||
tile.top = startRow * @lineHeight - @scrollTop
|
||||
tile.left = -@scrollLeft
|
||||
tile.height = @tileSize * @lineHeight
|
||||
tile.display = "block"
|
||||
|
||||
@updateLinesState(tile, startRow, endRow)
|
||||
|
||||
visibleTiles[startRow] = true
|
||||
|
||||
if @mouseWheelScreenRow? and @model.tokenizedLineForScreenRow(@mouseWheelScreenRow)?
|
||||
mouseWheelTile = @tileForRow(@mouseWheelScreenRow)
|
||||
|
||||
unless visibleTiles[mouseWheelTile]?
|
||||
@state.content.tiles[mouseWheelTile].display = "none"
|
||||
visibleTiles[mouseWheelTile] = true
|
||||
|
||||
for id, tile of @state.content.tiles
|
||||
continue if visibleTiles.hasOwnProperty(id)
|
||||
|
||||
delete @state.content.tiles[id]
|
||||
|
||||
updateLinesState: (tileState, startRow, endRow) ->
|
||||
tileState.lines ?= {}
|
||||
visibleLineIds = {}
|
||||
row = @startRow
|
||||
while row < @endRow
|
||||
row = startRow
|
||||
while row < endRow
|
||||
line = @model.tokenizedLineForScreenRow(row)
|
||||
unless line?
|
||||
throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}")
|
||||
|
||||
visibleLineIds[line.id] = true
|
||||
if @state.content.lines.hasOwnProperty(line.id)
|
||||
@updateLineState(row, line)
|
||||
if tileState.lines.hasOwnProperty(line.id)
|
||||
lineState = tileState.lines[line.id]
|
||||
lineState.screenRow = row
|
||||
lineState.top = (row - startRow) * @lineHeight
|
||||
lineState.decorationClasses = @lineDecorationClassesForRow(row)
|
||||
else
|
||||
@buildLineState(row, line)
|
||||
tileState.lines[line.id] =
|
||||
screenRow: row
|
||||
text: line.text
|
||||
openScopes: line.openScopes
|
||||
tags: line.tags
|
||||
specialTokens: line.specialTokens
|
||||
firstNonWhitespaceIndex: line.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line.firstTrailingWhitespaceIndex
|
||||
invisibles: line.invisibles
|
||||
endOfLineInvisibles: line.endOfLineInvisibles
|
||||
isOnlyWhitespace: line.isOnlyWhitespace()
|
||||
indentLevel: line.indentLevel
|
||||
tabLength: line.tabLength
|
||||
fold: line.fold
|
||||
top: (row - startRow) * @lineHeight
|
||||
decorationClasses: @lineDecorationClassesForRow(row)
|
||||
row++
|
||||
|
||||
if @mouseWheelScreenRow?
|
||||
if preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow)
|
||||
visibleLineIds[preservedLine.id] = true
|
||||
|
||||
for id, line of @state.content.lines
|
||||
unless visibleLineIds.hasOwnProperty(id)
|
||||
delete @state.content.lines[id]
|
||||
for id, line of tileState.lines
|
||||
delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id)
|
||||
return
|
||||
|
||||
updateLineState: (row, line) ->
|
||||
lineState = @state.content.lines[line.id]
|
||||
lineState.screenRow = row
|
||||
lineState.top = row * @lineHeight
|
||||
lineState.decorationClasses = @lineDecorationClassesForRow(row)
|
||||
|
||||
buildLineState: (row, line) ->
|
||||
@state.content.lines[line.id] =
|
||||
screenRow: row
|
||||
text: line.text
|
||||
openScopes: line.openScopes
|
||||
tags: line.tags
|
||||
specialTokens: line.specialTokens
|
||||
firstNonWhitespaceIndex: line.firstNonWhitespaceIndex
|
||||
firstTrailingWhitespaceIndex: line.firstTrailingWhitespaceIndex
|
||||
invisibles: line.invisibles
|
||||
endOfLineInvisibles: line.endOfLineInvisibles
|
||||
isOnlyWhitespace: line.isOnlyWhitespace()
|
||||
indentLevel: line.indentLevel
|
||||
tabLength: line.tabLength
|
||||
fold: line.fold
|
||||
top: row * @lineHeight
|
||||
decorationClasses: @lineDecorationClassesForRow(row)
|
||||
|
||||
updateCursorsState: ->
|
||||
@state.content.cursors = {}
|
||||
@updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation
|
||||
@@ -382,12 +410,10 @@ class TextEditorPresenter
|
||||
else
|
||||
screenPosition = decoration.getMarker().getHeadScreenPosition()
|
||||
|
||||
pixelPosition = @pixelPositionForScreenPosition(screenPosition)
|
||||
pixelPosition = @pixelPositionForScreenPosition(screenPosition, true)
|
||||
|
||||
{scrollTop, scrollLeft} = @state.content
|
||||
|
||||
top = pixelPosition.top + @lineHeight - scrollTop
|
||||
left = pixelPosition.left + @gutterWidth - scrollLeft
|
||||
top = pixelPosition.top + @lineHeight
|
||||
left = pixelPosition.left + @gutterWidth
|
||||
|
||||
if overlayDimensions = @overlayDimensions[decoration.id]
|
||||
{itemWidth, itemHeight, contentMargin} = overlayDimensions
|
||||
@@ -569,7 +595,7 @@ class TextEditorPresenter
|
||||
updateStartRow: ->
|
||||
return unless @scrollTop? and @lineHeight?
|
||||
|
||||
startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin
|
||||
startRow = Math.floor(@scrollTop / @lineHeight)
|
||||
@startRow = Math.max(0, startRow)
|
||||
|
||||
updateEndRow: ->
|
||||
@@ -577,7 +603,7 @@ class TextEditorPresenter
|
||||
|
||||
startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight))
|
||||
visibleLinesCount = Math.ceil(@height / @lineHeight) + 1
|
||||
endRow = startRow + visibleLinesCount + @lineOverdrawMargin
|
||||
endRow = startRow + visibleLinesCount
|
||||
@endRow = Math.min(@model.getScreenLineCount(), endRow)
|
||||
|
||||
updateScrollWidth: ->
|
||||
@@ -610,6 +636,7 @@ class TextEditorPresenter
|
||||
oldContentWidth = @contentWidth
|
||||
clip = @model.tokenizedLineForScreenRow(@model.getLongestScreenRow())?.isSoftWrapped()
|
||||
@contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), @model.getMaxScreenLineLength()], clip).left
|
||||
@contentWidth += @scrollLeft
|
||||
@contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
|
||||
|
||||
if @contentHeight isnt oldContentHeight
|
||||
@@ -755,7 +782,7 @@ class TextEditorPresenter
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateTilesState = true
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
@@ -778,7 +805,7 @@ class TextEditorPresenter
|
||||
@state.content.scrollingVertically = false
|
||||
if @mouseWheelScreenRow?
|
||||
@mouseWheelScreenRow = null
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateTilesState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
|
||||
@@ -792,8 +819,10 @@ class TextEditorPresenter
|
||||
@model.setScrollLeft(scrollLeft)
|
||||
@shouldUpdateHorizontalScrollState = true
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@shouldUpdateCursorsState = true unless oldScrollLeft?
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateOverlaysState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateTilesState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
@@ -841,7 +870,7 @@ class TextEditorPresenter
|
||||
@shouldUpdateVerticalScrollState = true
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateTilesState = true
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
@@ -869,7 +898,7 @@ class TextEditorPresenter
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateTilesState = true
|
||||
@shouldUpdateCursorsState = true unless oldContentFrameWidth?
|
||||
|
||||
@emitDidUpdateState()
|
||||
@@ -935,7 +964,7 @@ class TextEditorPresenter
|
||||
@shouldUpdateScrollbarsState = true
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateTilesState = true
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
@@ -943,9 +972,9 @@ class TextEditorPresenter
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setMouseWheelScreenRow: (mouseWheelScreenRow) ->
|
||||
unless @mouseWheelScreenRow is mouseWheelScreenRow
|
||||
@mouseWheelScreenRow = mouseWheelScreenRow
|
||||
setMouseWheelScreenRow: (screenRow) ->
|
||||
if @mouseWheelScreenRow isnt screenRow
|
||||
@mouseWheelScreenRow = screenRow
|
||||
@didStartScrolling()
|
||||
|
||||
setBaseCharacterWidth: (baseCharacterWidth) ->
|
||||
@@ -987,7 +1016,7 @@ class TextEditorPresenter
|
||||
@shouldUpdateHiddenInputState = true
|
||||
@shouldUpdateContentState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateTilesState = true
|
||||
@shouldUpdateCursorsState = true
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@@ -1028,10 +1057,13 @@ class TextEditorPresenter
|
||||
charLength = 1
|
||||
valueIndex++
|
||||
|
||||
return {top, left} if column is targetColumn
|
||||
break if column is targetColumn
|
||||
|
||||
left += characterWidths[char] ? baseCharacterWidth unless char is '\0'
|
||||
column += charLength
|
||||
|
||||
top -= @scrollTop
|
||||
left -= @scrollLeft
|
||||
{top, left}
|
||||
|
||||
hasPixelRectRequirements: ->
|
||||
@@ -1082,7 +1114,7 @@ class TextEditorPresenter
|
||||
intersectsVisibleRowRange = true
|
||||
|
||||
if intersectsVisibleRowRange
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
@shouldUpdateTilesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
@@ -1107,7 +1139,7 @@ class TextEditorPresenter
|
||||
decoration.getMarker().getScreenRange())
|
||||
@addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
|
||||
if decoration.isType('line') or Decoration.isType(oldProperties, 'line')
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateTilesState = true
|
||||
if decoration.isType('line-number') or Decoration.isType(oldProperties, 'line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
if (decoration.isType('gutter') and not decoration.isType('line-number')) or
|
||||
@@ -1123,7 +1155,7 @@ class TextEditorPresenter
|
||||
didDestroyDecoration: (decoration) ->
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
@shouldUpdateTilesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
@@ -1148,7 +1180,7 @@ class TextEditorPresenter
|
||||
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
@shouldUpdateTilesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
|
||||
@@ -60,7 +60,7 @@ class TextEditorView extends View
|
||||
placeholderText: placeholderText
|
||||
|
||||
element = new TextEditorElement
|
||||
element.lineOverdrawMargin = props?.lineOverdrawMargin
|
||||
element.tileSize = props?.tileSize
|
||||
element.setAttribute(name, value) for name, value of attributes if attributes?
|
||||
element.setModel(model)
|
||||
return element.__spacePenView
|
||||
|
||||
361
src/tile-component.coffee
Normal file
361
src/tile-component.coffee
Normal file
@@ -0,0 +1,361 @@
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
TokenIterator = require './token-iterator'
|
||||
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
|
||||
WrapperDiv = document.createElement('div')
|
||||
TokenTextEscapeRegex = /[&"'<>]/g
|
||||
MaxTokenLength = 20000
|
||||
|
||||
cloneObject = (object) ->
|
||||
clone = {}
|
||||
clone[key] = value for key, value of object
|
||||
clone
|
||||
|
||||
module.exports =
|
||||
class TileComponent
|
||||
placeholderTextDiv: null
|
||||
|
||||
constructor: ({@presenter, @id}) ->
|
||||
@tokenIterator = new TokenIterator
|
||||
@measuredLines = new Set
|
||||
@lineNodesByLineId = {}
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@domNode = document.createElement("div")
|
||||
@domNode.classList.add("tile")
|
||||
@domNode.style.position = "absolute"
|
||||
@domNode.style.display = "block"
|
||||
|
||||
getDomNode: ->
|
||||
@domNode
|
||||
|
||||
updateSync: (state) ->
|
||||
@newState = state
|
||||
unless @oldState
|
||||
@oldState = {tiles: {}}
|
||||
@oldState.tiles[@id] = {lines: {}}
|
||||
|
||||
@newTileState = @newState.tiles[@id]
|
||||
@oldTileState = @oldState.tiles[@id]
|
||||
|
||||
if @newTileState.display isnt @oldTileState.display
|
||||
@domNode.style.display = @newTileState.display
|
||||
@oldTileState.display = @newTileState.display
|
||||
|
||||
if @newTileState.height isnt @oldTileState.height
|
||||
@domNode.style.height = @newTileState.height + 'px'
|
||||
@oldTileState.height = @newTileState.height
|
||||
|
||||
if @newTileState.top isnt @oldTileState.top or @newTileState.left isnt @oldTileState.left
|
||||
@domNode.style['-webkit-transform'] = "translate3d(#{@newTileState.left}px, #{@newTileState.top}px, 0px)"
|
||||
@oldTileState.top = @newTileState.top
|
||||
@oldTileState.left = @newTileState.left
|
||||
|
||||
@removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
|
||||
@updateLineNodes()
|
||||
|
||||
if @newState.scrollWidth isnt @oldState.scrollWidth
|
||||
@domNode.style.width = @newState.scrollWidth + 'px'
|
||||
@oldState.scrollWidth = @newState.scrollWidth
|
||||
|
||||
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
|
||||
@oldState.scrollWidth = @newState.scrollWidth
|
||||
|
||||
removeLineNodes: ->
|
||||
@removeLineNode(id) for id of @oldTileState.lines
|
||||
return
|
||||
|
||||
removeLineNode: (id) ->
|
||||
@lineNodesByLineId[id].remove()
|
||||
delete @lineNodesByLineId[id]
|
||||
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
|
||||
delete @screenRowsByLineId[id]
|
||||
delete @oldTileState.lines[id]
|
||||
|
||||
updateLineNodes: ->
|
||||
for id of @oldTileState.lines
|
||||
unless @newTileState.lines.hasOwnProperty(id)
|
||||
@removeLineNode(id)
|
||||
|
||||
newLineIds = null
|
||||
newLinesHTML = null
|
||||
|
||||
for id, lineState of @newTileState.lines
|
||||
if @oldTileState.lines.hasOwnProperty(id)
|
||||
@updateLineNode(id)
|
||||
else
|
||||
newLineIds ?= []
|
||||
newLinesHTML ?= ""
|
||||
newLineIds.push(id)
|
||||
newLinesHTML += @buildLineHTML(id)
|
||||
@screenRowsByLineId[id] = lineState.screenRow
|
||||
@lineIdsByScreenRow[lineState.screenRow] = id
|
||||
@oldTileState.lines[id] = cloneObject(lineState)
|
||||
|
||||
return unless newLineIds?
|
||||
|
||||
WrapperDiv.innerHTML = newLinesHTML
|
||||
newLineNodes = _.toArray(WrapperDiv.children)
|
||||
for id, i in newLineIds
|
||||
lineNode = newLineNodes[i]
|
||||
@lineNodesByLineId[id] = lineNode
|
||||
@domNode.appendChild(lineNode)
|
||||
|
||||
return
|
||||
|
||||
buildLineHTML: (id) ->
|
||||
{scrollWidth} = @newState
|
||||
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
|
||||
|
||||
classes = ''
|
||||
if decorationClasses?
|
||||
for decorationClass in decorationClasses
|
||||
classes += decorationClass + ' '
|
||||
classes += 'line'
|
||||
|
||||
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{scrollWidth}px;\" data-screen-row=\"#{screenRow}\">"
|
||||
|
||||
if text is ""
|
||||
lineHTML += @buildEmptyLineInnerHTML(id)
|
||||
else
|
||||
lineHTML += @buildLineInnerHTML(id)
|
||||
|
||||
lineHTML += '<span class="fold-marker"></span>' if fold
|
||||
lineHTML += "</div>"
|
||||
lineHTML
|
||||
|
||||
buildEmptyLineInnerHTML: (id) ->
|
||||
{indentGuidesVisible} = @newState
|
||||
{indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]
|
||||
|
||||
if indentGuidesVisible and indentLevel > 0
|
||||
invisibleIndex = 0
|
||||
lineHTML = ''
|
||||
for i in [0...indentLevel]
|
||||
lineHTML += "<span class='indent-guide'>"
|
||||
for j in [0...tabLength]
|
||||
if invisible = endOfLineInvisibles?[invisibleIndex++]
|
||||
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
|
||||
else
|
||||
lineHTML += ' '
|
||||
lineHTML += "</span>"
|
||||
|
||||
while invisibleIndex < endOfLineInvisibles?.length
|
||||
lineHTML += "<span class='invisible-character'>#{endOfLineInvisibles[invisibleIndex++]}</span>"
|
||||
|
||||
lineHTML
|
||||
else
|
||||
@buildEndOfLineHTML(id) or ' '
|
||||
|
||||
buildLineInnerHTML: (id) ->
|
||||
lineState = @newTileState.lines[id]
|
||||
{firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, invisibles} = lineState
|
||||
lineIsWhitespaceOnly = firstTrailingWhitespaceIndex is 0
|
||||
|
||||
innerHTML = ""
|
||||
@tokenIterator.reset(lineState)
|
||||
|
||||
while @tokenIterator.next()
|
||||
for scope in @tokenIterator.getScopeEnds()
|
||||
innerHTML += "</span>"
|
||||
|
||||
for scope in @tokenIterator.getScopeStarts()
|
||||
innerHTML += "<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
|
||||
|
||||
tokenStart = @tokenIterator.getScreenStart()
|
||||
tokenEnd = @tokenIterator.getScreenEnd()
|
||||
tokenText = @tokenIterator.getText()
|
||||
isHardTab = @tokenIterator.isHardTab()
|
||||
|
||||
if hasLeadingWhitespace = tokenStart < firstNonWhitespaceIndex
|
||||
tokenFirstNonWhitespaceIndex = firstNonWhitespaceIndex - tokenStart
|
||||
else
|
||||
tokenFirstNonWhitespaceIndex = null
|
||||
|
||||
if hasTrailingWhitespace = tokenEnd > firstTrailingWhitespaceIndex
|
||||
tokenFirstTrailingWhitespaceIndex = Math.max(0, firstTrailingWhitespaceIndex - tokenStart)
|
||||
else
|
||||
tokenFirstTrailingWhitespaceIndex = null
|
||||
|
||||
hasIndentGuide =
|
||||
@newState.indentGuidesVisible and
|
||||
(hasLeadingWhitespace or lineIsWhitespaceOnly)
|
||||
|
||||
hasInvisibleCharacters =
|
||||
(invisibles?.tab and isHardTab) or
|
||||
(invisibles?.space and (hasLeadingWhitespace or hasTrailingWhitespace))
|
||||
|
||||
innerHTML += @buildTokenHTML(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters)
|
||||
|
||||
for scope in @tokenIterator.getScopeEnds()
|
||||
innerHTML += "</span>"
|
||||
|
||||
for scope in @tokenIterator.getScopes()
|
||||
innerHTML += "</span>"
|
||||
|
||||
innerHTML += @buildEndOfLineHTML(id)
|
||||
innerHTML
|
||||
|
||||
buildTokenHTML: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
|
||||
if isHardTab
|
||||
classes = 'hard-tab'
|
||||
classes += ' leading-whitespace' if firstNonWhitespaceIndex?
|
||||
classes += ' trailing-whitespace' if firstTrailingWhitespaceIndex?
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if hasInvisibleCharacters
|
||||
return "<span class='#{classes}'>#{@escapeTokenText(tokenText)}</span>"
|
||||
else
|
||||
startIndex = 0
|
||||
endIndex = tokenText.length
|
||||
|
||||
leadingHtml = ''
|
||||
trailingHtml = ''
|
||||
|
||||
if firstNonWhitespaceIndex?
|
||||
leadingWhitespace = tokenText.substring(0, firstNonWhitespaceIndex)
|
||||
|
||||
classes = 'leading-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if hasInvisibleCharacters
|
||||
|
||||
leadingHtml = "<span class='#{classes}'>#{leadingWhitespace}</span>"
|
||||
startIndex = firstNonWhitespaceIndex
|
||||
|
||||
if firstTrailingWhitespaceIndex?
|
||||
tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
|
||||
trailingWhitespace = tokenText.substring(firstTrailingWhitespaceIndex)
|
||||
|
||||
classes = 'trailing-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
|
||||
classes += ' invisible-character' if hasInvisibleCharacters
|
||||
|
||||
trailingHtml = "<span class='#{classes}'>#{trailingWhitespace}</span>"
|
||||
|
||||
endIndex = firstTrailingWhitespaceIndex
|
||||
|
||||
html = leadingHtml
|
||||
if tokenText.length > MaxTokenLength
|
||||
while startIndex < endIndex
|
||||
html += "<span>" + @escapeTokenText(tokenText, startIndex, startIndex + MaxTokenLength) + "</span>"
|
||||
startIndex += MaxTokenLength
|
||||
else
|
||||
html += @escapeTokenText(tokenText, startIndex, endIndex)
|
||||
|
||||
html += trailingHtml
|
||||
html
|
||||
|
||||
escapeTokenText: (tokenText, startIndex, endIndex) ->
|
||||
if startIndex? and endIndex? and startIndex > 0 or endIndex < tokenText.length
|
||||
tokenText = tokenText.slice(startIndex, endIndex)
|
||||
tokenText.replace(TokenTextEscapeRegex, @escapeTokenTextReplace)
|
||||
|
||||
escapeTokenTextReplace: (match) ->
|
||||
switch match
|
||||
when '&' then '&'
|
||||
when '"' then '"'
|
||||
when "'" then '''
|
||||
when '<' then '<'
|
||||
when '>' then '>'
|
||||
else match
|
||||
|
||||
buildEndOfLineHTML: (id) ->
|
||||
{endOfLineInvisibles} = @newTileState.lines[id]
|
||||
|
||||
html = ''
|
||||
if endOfLineInvisibles?
|
||||
for invisible in endOfLineInvisibles
|
||||
html += "<span class='invisible-character'>#{invisible}</span>"
|
||||
html
|
||||
|
||||
updateLineNode: (id) ->
|
||||
oldLineState = @oldTileState.lines[id]
|
||||
newLineState = @newTileState.lines[id]
|
||||
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
|
||||
if @newState.scrollWidth isnt @oldState.scrollWidth
|
||||
lineNode.style.width = @newState.scrollWidth + 'px'
|
||||
|
||||
newDecorationClasses = newLineState.decorationClasses
|
||||
oldDecorationClasses = oldLineState.decorationClasses
|
||||
|
||||
if oldDecorationClasses?
|
||||
for decorationClass in oldDecorationClasses
|
||||
unless newDecorationClasses? and decorationClass in newDecorationClasses
|
||||
lineNode.classList.remove(decorationClass)
|
||||
|
||||
if newDecorationClasses?
|
||||
for decorationClass in newDecorationClasses
|
||||
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
|
||||
lineNode.classList.add(decorationClass)
|
||||
|
||||
oldLineState.decorationClasses = newLineState.decorationClasses
|
||||
|
||||
if newLineState.top isnt oldLineState.top
|
||||
lineNode.style.top = newLineState.top + 'px'
|
||||
oldLineState.top = newLineState.top
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
lineNode.dataset.screenRow = newLineState.screenRow
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
@lineIdsByScreenRow[newLineState.screenRow] = id
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||
|
||||
measureCharactersInNewLines: ->
|
||||
for id, lineState of @oldTileState.lines
|
||||
unless @measuredLines.has(id)
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
@measureCharactersInLine(id, lineState, lineNode)
|
||||
return
|
||||
|
||||
measureCharactersInLine: (lineId, tokenizedLine, lineNode) ->
|
||||
rangeForMeasurement = null
|
||||
iterator = null
|
||||
charIndex = 0
|
||||
|
||||
@tokenIterator.reset(tokenizedLine)
|
||||
while @tokenIterator.next()
|
||||
scopes = @tokenIterator.getScopes()
|
||||
text = @tokenIterator.getText()
|
||||
charWidths = @presenter.getScopedCharacterWidths(scopes)
|
||||
|
||||
textIndex = 0
|
||||
while textIndex < text.length
|
||||
if @tokenIterator.isPairedCharacter()
|
||||
char = text
|
||||
charLength = 2
|
||||
textIndex += 2
|
||||
else
|
||||
char = text[textIndex]
|
||||
charLength = 1
|
||||
textIndex++
|
||||
|
||||
continue if char is '\0'
|
||||
|
||||
unless charWidths[char]?
|
||||
unless textNode?
|
||||
rangeForMeasurement ?= document.createRange()
|
||||
iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter)
|
||||
textNode = iterator.nextNode()
|
||||
textNodeIndex = 0
|
||||
nextTextNodeIndex = textNode.textContent.length
|
||||
|
||||
while nextTextNodeIndex <= charIndex
|
||||
textNode = iterator.nextNode()
|
||||
textNodeIndex = nextTextNodeIndex
|
||||
nextTextNodeIndex = textNodeIndex + textNode.textContent.length
|
||||
|
||||
i = charIndex - textNodeIndex
|
||||
rangeForMeasurement.setStart(textNode, i)
|
||||
rangeForMeasurement.setEnd(textNode, i + charLength)
|
||||
charWidth = rangeForMeasurement.getBoundingClientRect().width
|
||||
@presenter.setScopedCharacterWidth(scopes, char, charWidth)
|
||||
|
||||
charIndex += charLength
|
||||
|
||||
@measuredLines.add(lineId)
|
||||
|
||||
clearMeasurements: ->
|
||||
@measuredLines.clear()
|
||||
Reference in New Issue
Block a user