diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 12d189035..b2bb09acd 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -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' diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 1ad18da5a..142026913 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -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 = [] diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 17c904e99..23f1c0015 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -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 = "