diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index a9e98efc1..358fd099f 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -7,7 +7,7 @@ nbsp = String.fromCharCode(160) describe "TextEditorComponent", -> [contentNode, editor, wrapperView, wrapperNode, component, componentNode, verticalScrollbarNode, horizontalScrollbarNode] = [] - [lineHeightInPixels, charWidth, nextAnimationFrame, noAnimationFrame, tileSize] = [] + [lineHeightInPixels, charWidth, nextAnimationFrame, noAnimationFrame, tileSize, tileHeightInPixels] = [] beforeEach -> tileSize = 3 @@ -45,6 +45,7 @@ describe "TextEditorComponent", -> component.setFontSize(20) lineHeightInPixels = editor.getLineHeightInPixels() + tileHeightInPixels = tileSize * lineHeightInPixels charWidth = editor.getDefaultCharWidth() componentNode = component.getDomNode() verticalScrollbarNode = componentNode.querySelector('.vertical-scrollbar') @@ -89,11 +90,10 @@ describe "TextEditorComponent", -> it "renders the currently-visible lines in a tiled fashion", -> wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px' - tileHeight = tileSize * lineHeightInPixels component.measureDimensions() nextAnimationFrame() - tilesNodes = componentNode.querySelectorAll(".tile") + tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") expect(tilesNodes.length).toBe(3) @@ -103,13 +103,13 @@ describe "TextEditorComponent", -> 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].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeightInPixels}px, 0px)" expect(tilesNodes[1].querySelectorAll(".line").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].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeightInPixels}px, 0px)" expect(tilesNodes[2].querySelectorAll(".line").length).toBe(tileSize) expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels) expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels) @@ -121,24 +121,24 @@ describe "TextEditorComponent", -> verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() - tilesNodes = componentNode.querySelectorAll(".tile") + tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") expect(component.lineNodeForScreenRow(2)).toBeUndefined() expect(tilesNodes.length).toBe(3) - expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, -5px, 0px)" + expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, #{0 * tileHeightInPixels - 5}px, 0px)" expect(tilesNodes[0].querySelectorAll(".line").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].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeightInPixels - 5}px, 0px)" expect(tilesNodes[1].querySelectorAll(".line").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].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeightInPixels - 5}px, 0px)" expect(tilesNodes[2].querySelectorAll(".line").length).toBe(tileSize) expectTileContainsRow(tilesNodes[2], 9, top: 0 * lineHeightInPixels) expectTileContainsRow(tilesNodes[2], 10, top: 1 * lineHeightInPixels) @@ -146,19 +146,18 @@ describe "TextEditorComponent", -> 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() - tilesNodes = componentNode.querySelectorAll(".tile") + tilesNodes = componentNode.querySelector(".lines").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)" + expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeightInPixels}px, 0px)" expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels) expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels) expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels) @@ -166,19 +165,19 @@ describe "TextEditorComponent", -> editor.getBuffer().insert([0, 0], '\n\n') nextAnimationFrame() - tilesNodes = componentNode.querySelectorAll(".tile") + tilesNodes = componentNode.querySelector(".lines").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)" + expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeightInPixels}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)" + expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeightInPixels}px, 0px)" expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels) expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels) expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels) @@ -272,16 +271,22 @@ describe "TextEditorComponent", -> atom.config.set("editor.showInvisibles", false) expect(component.lineNodeForScreenRow(10).textContent).toBe nbsp - it "gives the lines div the same background color as the editor to improve GPU performance", -> + it "gives the lines and tiles divs the same background color as the editor to improve GPU performance", -> linesNode = componentNode.querySelector('.lines') backgroundColor = getComputedStyle(wrapperNode).backgroundColor expect(linesNode.style.backgroundColor).toBe backgroundColor + for tileNode in linesNode.querySelectorAll(".tile") + expect(tileNode.style.backgroundColor).toBe(backgroundColor) + wrapperNode.style.backgroundColor = 'rgb(255, 0, 0)' advanceClock(atom.views.documentPollingInterval) nextAnimationFrame() expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)' + for tileNode in linesNode.querySelectorAll(".tile") + expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)") + it "applies .leading-whitespace for lines with leading spaces and/or tabs", -> editor.setText(' a') @@ -552,47 +557,90 @@ describe "TextEditorComponent", -> expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy() describe "gutter rendering", -> - it "renders the currently-visible line numbers", -> + expectTileContainsRow = (tileNode, screenRow, {top, text}) -> + lineNode = tileNode.querySelector("[data-screen-row='#{screenRow}']") + + expect(lineNode.offsetTop).toBe(top) + expect(lineNode.textContent).toBe(text) + + it "renders the currently-visible line numbers in a tiled fashion", -> wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' component.measureDimensions() nextAnimationFrame() - 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" + tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile") - verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels + expect(tilesNodes.length).toBe(3) + expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" + + expect(tilesNodes[0].querySelectorAll('.line-number').length).toBe 3 + expectTileContainsRow(tilesNodes[0], 0, top: lineHeightInPixels * 0, text: "#{nbsp}1") + expectTileContainsRow(tilesNodes[0], 1, top: lineHeightInPixels * 1, text: "#{nbsp}2") + expectTileContainsRow(tilesNodes[0], 2, top: lineHeightInPixels * 2, text: "#{nbsp}3") + + expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeightInPixels}px, 0px)" + expect(tilesNodes[1].querySelectorAll('.line-number').length).toBe 3 + expectTileContainsRow(tilesNodes[1], 3, top: lineHeightInPixels * 0, text: "#{nbsp}4") + expectTileContainsRow(tilesNodes[1], 4, top: lineHeightInPixels * 1, text: "#{nbsp}5") + expectTileContainsRow(tilesNodes[1], 5, top: lineHeightInPixels * 2, text: "#{nbsp}6") + + expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeightInPixels}px, 0px)" + expect(tilesNodes[2].querySelectorAll('.line-number').length).toBe 3 + expectTileContainsRow(tilesNodes[2], 6, top: lineHeightInPixels * 0, text: "#{nbsp}7") + expectTileContainsRow(tilesNodes[2], 7, top: lineHeightInPixels * 1, text: "#{nbsp}8") + expectTileContainsRow(tilesNodes[2], 8, top: lineHeightInPixels * 2, text: "#{nbsp}9") + + verticalScrollbarNode.scrollTop = tileSize * lineHeightInPixels + 5 verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() - expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # visible line-numbers + dummy line number + tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile") - expect(component.lineNumberNodeForScreenRow(2).textContent).toBe "#{nbsp}3" - expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels - expect(component.lineNumberNodeForScreenRow(7).textContent).toBe "#{nbsp}8" - expect(component.lineNumberNodeForScreenRow(7).offsetTop).toBe 7 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(2)).toBeUndefined() + expect(tilesNodes.length).toBe(3) + + expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, #{0 * tileHeightInPixels - 5}px, 0px)" + expect(tilesNodes[0].querySelectorAll(".line-number").length).toBe(tileSize) + expectTileContainsRow(tilesNodes[0], 3, top: lineHeightInPixels * 0, text: "#{nbsp}4") + expectTileContainsRow(tilesNodes[0], 4, top: lineHeightInPixels * 1, text: "#{nbsp}5") + expectTileContainsRow(tilesNodes[0], 5, top: lineHeightInPixels * 2, text: "#{nbsp}6") + + expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeightInPixels - 5}px, 0px)" + expect(tilesNodes[1].querySelectorAll(".line-number").length).toBe(tileSize) + expectTileContainsRow(tilesNodes[1], 6, top: 0 * lineHeightInPixels, text: "#{nbsp}7") + expectTileContainsRow(tilesNodes[1], 7, top: 1 * lineHeightInPixels, text: "#{nbsp}8") + expectTileContainsRow(tilesNodes[1], 8, top: 2 * lineHeightInPixels, text: "#{nbsp}9") + + expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeightInPixels - 5}px, 0px)" + expect(tilesNodes[2].querySelectorAll(".line-number").length).toBe(tileSize) + expectTileContainsRow(tilesNodes[2], 9, top: 0 * lineHeightInPixels, text: "10") + expectTileContainsRow(tilesNodes[2], 10, top: 1 * lineHeightInPixels, text: "11") + expectTileContainsRow(tilesNodes[2], 11, top: 2 * lineHeightInPixels, text: "12") it "updates the translation of subsequent line numbers when lines are inserted or removed", -> editor.getBuffer().insert([0, 0], '\n\n') nextAnimationFrame() lineNumberNodes = componentNode.querySelectorAll('.line-number') - expect(component.lineNumberNodeForScreenRow(0).offsetTop).toBe 0 + expect(component.lineNumberNodeForScreenRow(0).offsetTop).toBe 0 * lineHeightInPixels expect(component.lineNumberNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels - expect(component.lineNumberNodeForScreenRow(3).offsetTop).toBe 3 * lineHeightInPixels - expect(component.lineNumberNodeForScreenRow(4).offsetTop).toBe 4 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(3).offsetTop).toBe 0 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(4).offsetTop).toBe 1 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(5).offsetTop).toBe 2 * lineHeightInPixels editor.getBuffer().insert([0, 0], '\n\n') nextAnimationFrame() - expect(component.lineNumberNodeForScreenRow(0).offsetTop).toBe 0 + expect(component.lineNumberNodeForScreenRow(0).offsetTop).toBe 0 * lineHeightInPixels expect(component.lineNumberNodeForScreenRow(1).offsetTop).toBe 1 * lineHeightInPixels expect(component.lineNumberNodeForScreenRow(2).offsetTop).toBe 2 * lineHeightInPixels - expect(component.lineNumberNodeForScreenRow(3).offsetTop).toBe 3 * lineHeightInPixels - expect(component.lineNumberNodeForScreenRow(4).offsetTop).toBe 4 * lineHeightInPixels - expect(component.lineNumberNodeForScreenRow(5).offsetTop).toBe 5 * lineHeightInPixels - expect(component.lineNumberNodeForScreenRow(6).offsetTop).toBe 6 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(3).offsetTop).toBe 0 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(4).offsetTop).toBe 1 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(5).offsetTop).toBe 2 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(6).offsetTop).toBe 0 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(7).offsetTop).toBe 1 * lineHeightInPixels + expect(component.lineNumberNodeForScreenRow(8).offsetTop).toBe 2 * lineHeightInPixels it "renders • characters for soft-wrapped lines", -> editor.setSoftWrapped(true) @@ -601,13 +649,16 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - expect(componentNode.querySelectorAll('.line-number').length).toBe 6 + 1 # 1 dummy line + expect(componentNode.querySelectorAll('.line-number').length).toBe 9 + 1 # 3 line-numbers tiles + 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" expect(component.lineNumberNodeForScreenRow(3).textContent).toBe "#{nbsp}•" expect(component.lineNumberNodeForScreenRow(4).textContent).toBe "#{nbsp}3" expect(component.lineNumberNodeForScreenRow(5).textContent).toBe "#{nbsp}•" + expect(component.lineNumberNodeForScreenRow(6).textContent).toBe "#{nbsp}4" + expect(component.lineNumberNodeForScreenRow(7).textContent).toBe "#{nbsp}•" + expect(component.lineNumberNodeForScreenRow(8).textContent).toBe "#{nbsp}•" it "pads line numbers to be right-justified based on the maximum number of line number digits", -> editor.getBuffer().setText([1..10].join('\n')) @@ -645,12 +696,16 @@ describe "TextEditorComponent", -> lineNumbersNode = gutterNode.querySelector('.line-numbers') {backgroundColor} = getComputedStyle(wrapperNode) expect(lineNumbersNode.style.backgroundColor).toBe backgroundColor + for tileNode in lineNumbersNode.querySelectorAll(".tile") + expect(tileNode.style.backgroundColor).toBe(backgroundColor) # favor gutter color if it's assigned gutterNode.style.backgroundColor = 'rgb(255, 0, 0)' advanceClock(atom.views.documentPollingInterval) nextAnimationFrame() expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)' + for tileNode in lineNumbersNode.querySelectorAll(".tile") + expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)") it "hides or shows the gutter based on the '::isLineNumberGutterVisible' property on the model and the global 'editor.showLineNumbers' config setting", -> expect(component.gutterContainerComponent.getLineNumberGutterComponent()?).toBe true @@ -978,7 +1033,7 @@ describe "TextEditorComponent", -> it "renders 2 regions for 2-line selections", -> editor.setSelectedScreenRange([[1, 6], [2, 10]]) nextAnimationFrame() - tileNode = componentNode.querySelectorAll(".tile")[0] + tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0] regions = tileNode.querySelectorAll('.selection .region') expect(regions.length).toBe 2 @@ -999,7 +1054,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() # Tile 0 - tileNode = componentNode.querySelectorAll(".tile")[0] + tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0] regions = tileNode.querySelectorAll('.selection .region') expect(regions.length).toBe(3) @@ -1022,7 +1077,7 @@ describe "TextEditorComponent", -> expect(region3Rect.right).toBe tileNode.getBoundingClientRect().right # Tile 3 - tileNode = componentNode.querySelectorAll(".tile")[1] + tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[1] regions = tileNode.querySelectorAll('.selection .region') expect(regions.length).toBe(3) @@ -2059,7 +2114,7 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - tilesNodes = componentNode.querySelectorAll(".tile") + tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile") top = 0 for tileNode in tilesNodes diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index a0ce19b96..5acbc447a 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -58,6 +58,157 @@ describe "TextEditorPresenter", -> expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn) + tiledContentContract = (stateFn) -> + it "contains states for tiles that are visible on screen", -> + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) + + expectValues stateFn(presenter).tiles[0], { + top: 0 + } + expectValues stateFn(presenter).tiles[2], { + top: 2 + } + expectValues stateFn(presenter).tiles[4], { + top: 4 + } + expectValues stateFn(presenter).tiles[6], { + top: 6 + } + + expect(stateFn(presenter).tiles[8]).toBeUndefined() + + expectStateUpdate presenter, -> presenter.setScrollTop(3) + + expect(stateFn(presenter).tiles[0]).toBeUndefined() + + expectValues stateFn(presenter).tiles[2], { + top: -1 + } + expectValues stateFn(presenter).tiles[4], { + top: 1 + } + expectValues stateFn(presenter).tiles[6], { + top: 3 + } + expectValues stateFn(presenter).tiles[8], { + top: 5 + } + expectValues stateFn(presenter).tiles[10], { + top: 7 + } + + expect(stateFn(presenter).tiles[12]).toBeUndefined() + + it "includes state for all tiles if no external ::explicitHeight is assigned", -> + presenter = buildPresenter(explicitHeight: null, tileSize: 2) + expect(stateFn(presenter).tiles[0]).toBeDefined() + expect(stateFn(presenter).tiles[12]).toBeDefined() + + it "is empty until all of the required measurements are assigned", -> + presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null) + expect(stateFn(presenter).tiles).toEqual({}) + + presenter.setExplicitHeight(25) + expect(stateFn(presenter).tiles).toEqual({}) + + presenter.setLineHeight(10) + expect(stateFn(presenter).tiles).toEqual({}) + + presenter.setScrollTop(0) + expect(stateFn(presenter).tiles).not.toEqual({}) + + it "updates when ::scrollTop changes", -> + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) + + expect(stateFn(presenter).tiles[0]).toBeDefined() + expect(stateFn(presenter).tiles[2]).toBeDefined() + expect(stateFn(presenter).tiles[4]).toBeDefined() + expect(stateFn(presenter).tiles[6]).toBeDefined() + expect(stateFn(presenter).tiles[8]).toBeUndefined() + + expectStateUpdate presenter, -> presenter.setScrollTop(2) + + expect(stateFn(presenter).tiles[0]).toBeUndefined() + expect(stateFn(presenter).tiles[2]).toBeDefined() + expect(stateFn(presenter).tiles[4]).toBeDefined() + expect(stateFn(presenter).tiles[6]).toBeDefined() + expect(stateFn(presenter).tiles[8]).toBeDefined() + expect(stateFn(presenter).tiles[10]).toBeUndefined() + + it "updates when ::explicitHeight changes", -> + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) + + expect(stateFn(presenter).tiles[0]).toBeDefined() + expect(stateFn(presenter).tiles[2]).toBeDefined() + expect(stateFn(presenter).tiles[4]).toBeDefined() + expect(stateFn(presenter).tiles[6]).toBeDefined() + expect(stateFn(presenter).tiles[8]).toBeUndefined() + + expectStateUpdate presenter, -> presenter.setExplicitHeight(8) + + expect(stateFn(presenter).tiles[0]).toBeDefined() + expect(stateFn(presenter).tiles[2]).toBeDefined() + expect(stateFn(presenter).tiles[4]).toBeDefined() + expect(stateFn(presenter).tiles[6]).toBeDefined() + expect(stateFn(presenter).tiles[8]).toBeDefined() + expect(stateFn(presenter).tiles[10]).toBeUndefined() + + + it "updates when ::lineHeight changes", -> + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) + + expect(stateFn(presenter).tiles[0]).toBeDefined() + expect(stateFn(presenter).tiles[2]).toBeDefined() + expect(stateFn(presenter).tiles[4]).toBeDefined() + expect(stateFn(presenter).tiles[6]).toBeDefined() + expect(stateFn(presenter).tiles[8]).toBeUndefined() + + expectStateUpdate presenter, -> presenter.setLineHeight(2) + + expect(stateFn(presenter).tiles[0]).toBeDefined() + expect(stateFn(presenter).tiles[2]).toBeDefined() + expect(stateFn(presenter).tiles[4]).toBeDefined() + expect(stateFn(presenter).tiles[6]).toBeUndefined() + + 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(stateFn(presenter).tiles[0]).toBeDefined() + expect(stateFn(presenter).tiles[6]).toBeDefined() + expect(stateFn(presenter).tiles[8]).toBeUndefined() + + presenter.setMouseWheelScreenRow(0) + expectStateUpdate presenter, -> presenter.setScrollTop(4) + + expect(stateFn(presenter).tiles[0]).toBeDefined() + expect(stateFn(presenter).tiles[2]).toBeUndefined() + expect(stateFn(presenter).tiles[4]).toBeDefined() + expect(stateFn(presenter).tiles[12]).toBeUndefined() + + expectStateUpdate presenter, -> advanceClock(200) + + expect(stateFn(presenter).tiles[0]).toBeUndefined() + expect(stateFn(presenter).tiles[2]).toBeUndefined() + expect(stateFn(presenter).tiles[4]).toBeDefined() + expect(stateFn(presenter).tiles[12]).toBeUndefined() + + + # should clear ::mouseWheelScreenRow after stoppedScrollingDelay elapses even if we don't scroll first + presenter.setMouseWheelScreenRow(4) + advanceClock(200) + expectStateUpdate presenter, -> presenter.setScrollTop(6) + expect(stateFn(presenter).tiles[4]).toBeUndefined() + + 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(2) + + expectStateUpdate presenter, -> editor.setText("") + + expect(stateFn(presenter).tiles[2]).toBeUndefined() + expect(stateFn(presenter).tiles[0]).toBeDefined() + describe "during state retrieval", -> it "does not trigger onDidUpdateState events", -> presenter = buildPresenter() @@ -662,178 +813,7 @@ describe "TextEditorPresenter", -> tileRow = presenter.tileForRow(row) presenter.getState().content.tiles[tileRow]?.lines[lineId] - it "contains states for tiles that are visible on screen", -> - presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) - - 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 - } - - 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 - } - - expect(presenter.getState().content.tiles[12]).toBeUndefined() - - 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.tiles).toEqual({}) - - presenter.setExplicitHeight(25) - expect(presenter.getState().content.tiles).toEqual({}) - - presenter.setLineHeight(10) - expect(presenter.getState().content.tiles).toEqual({}) - - presenter.setScrollTop(0) - expect(presenter.getState().content.tiles).not.toEqual({}) - - it "updates when ::scrollTop changes", -> - presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) - - 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(2) - - 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: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) - - 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.setExplicitHeight(8) - - 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() - - - it "updates when ::lineHeight changes", -> - presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2) - - 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(2) - - 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, tileSize: 2) - - expectStateUpdate presenter, -> buffer.insert([2, 0], "hello\nworld\n") - - line1 = editor.tokenizedLineForScreenRow(1) - expectValues lineStateForScreenRow(presenter, 1), { - text: line1.text - tags: line1.tags - } - - line2 = editor.tokenizedLineForScreenRow(2) - expectValues lineStateForScreenRow(presenter, 2), { - text: line2.text - tags: line2.tags - } - - line3 = editor.tokenizedLineForScreenRow(3) - expectValues lineStateForScreenRow(presenter, 3), { - text: line3.text - tags: line3.tags - } - - 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(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(4) - - 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(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(4) - advanceClock(200) - expectStateUpdate presenter, -> presenter.setScrollTop(6) - expect(presenter.getState().content.tiles[4]).toBeUndefined() - - 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(2) - - expectStateUpdate presenter, -> editor.setText("") - - expect(presenter.getState().content.tiles[2]).toBeUndefined() - expect(presenter.getState().content.tiles[0]).toBeDefined() + tiledContentContract (presenter) -> presenter.getState().content describe "[tileId].lines[lineId]", -> # line state objects it "includes the state for visible lines in a tile", -> @@ -915,6 +895,29 @@ describe "TextEditorPresenter", -> expect(lineStateForScreenRow(presenter, 9)).toBeUndefined() + it "updates when the editor's content changes", -> + presenter = buildPresenter(explicitHeight: 25, scrollTop: 10, lineHeight: 10, tileSize: 2) + + expectStateUpdate presenter, -> buffer.insert([2, 0], "hello\nworld\n") + + line1 = editor.tokenizedLineForScreenRow(1) + expectValues lineStateForScreenRow(presenter, 1), { + text: line1.text + tags: line1.tags + } + + line2 = editor.tokenizedLineForScreenRow(2) + expectValues lineStateForScreenRow(presenter, 2), { + text: line2.text + tags: line2.tags + } + + line3 = editor.tokenizedLineForScreenRow(3) + expectValues lineStateForScreenRow(presenter, 3), { + text: line3.text + tags: line3.tags + } + 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) @@ -2007,9 +2010,10 @@ describe "TextEditorPresenter", -> editor.setText("1\n2\n3") expect(getLineNumberGutterState(presenter).content.maxLineNumberDigits).toBe 1 - describe ".content.lineNumbers", -> + describe ".content.tiles", -> lineNumberStateForScreenRow = (presenter, screenRow) -> editor = presenter.model + tileRow = presenter.tileForRow(screenRow) bufferRow = editor.bufferRowForScreenRow(screenRow) wrapCount = screenRow - editor.screenRowForBufferRow(bufferRow) if wrapCount > 0 @@ -2017,291 +2021,224 @@ describe "TextEditorPresenter", -> else key = bufferRow - getLineNumberGutterState(presenter).content.lineNumbers[key] + gutterState = getLineNumberGutterState(presenter) + gutterState.content.tiles[tileRow]?.lineNumbers[key] - 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) + tiledContentContract (presenter) -> getLineNumberGutterState(presenter).content - 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} - expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined() - - it "includes states for all line numbers if no ::explicitHeight is assigned", -> - presenter = buildPresenter(explicitHeight: null) - expect(lineNumberStateForScreenRow(presenter, 0)).toBeDefined() - expect(lineNumberStateForScreenRow(presenter, 12)).toBeDefined() - - it "updates when ::scrollTop changes", -> - editor.foldBufferRow(4) - editor.setSoftWrapped(true) - editor.setEditorWidthInChars(50) - presenter = buildPresenter(explicitHeight: 25, scrollTop: 30) - - 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, 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) - - expect(lineNumberStateForScreenRow(presenter, 1)).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, 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) - - expectValues lineNumberStateForScreenRow(presenter, 0), {bufferRow: 0} - expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} - expect(lineNumberStateForScreenRow(presenter, 4)).toBeUndefined() - - expectStateUpdate presenter, -> presenter.setLineHeight(5) - - expectValues lineNumberStateForScreenRow(presenter, 0), {bufferRow: 0} - expectValues lineNumberStateForScreenRow(presenter, 5), {bufferRow: 4} - expect(lineNumberStateForScreenRow(presenter, 6)).toBeUndefined() - - it "updates when the editor's content changes", -> - editor.foldBufferRow(4) - editor.setSoftWrapped(true) - editor.setEditorWidthInChars(50) - presenter = buildPresenter(explicitHeight: 35, scrollTop: 30) - - expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined() - expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} - expectValues lineNumberStateForScreenRow(presenter, 4), {bufferRow: 3} - expectValues lineNumberStateForScreenRow(presenter, 5), {bufferRow: 4} - expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7} - expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8} - expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() - - expectStateUpdate presenter, -> - editor.getBuffer().insert([3, Infinity], new Array(25).join("x ")) - - expect(lineNumberStateForScreenRow(presenter, 2)).toBeUndefined() - expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} - expectValues lineNumberStateForScreenRow(presenter, 4), {bufferRow: 3} - expectValues lineNumberStateForScreenRow(presenter, 5), {bufferRow: 3} - expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 4} - expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 7} - expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() - - it "does not remove out-of-view line numbers corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", -> - presenter = buildPresenter(explicitHeight: 25, stoppedScrollingDelay: 200) - - expect(lineNumberStateForScreenRow(presenter, 0)).toBeDefined() - 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, 6)).toBeDefined() - expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined() - - expectStateUpdate presenter, -> advanceClock(200) - - expect(lineNumberStateForScreenRow(presenter, 0)).toBeUndefined() - expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() - expect(lineNumberStateForScreenRow(presenter, 6)).toBeDefined() - expect(lineNumberStateForScreenRow(presenter, 7)).toBeUndefined() - - it "correctly handles the first screen line being soft-wrapped", -> - editor.setSoftWrapped(true) - editor.setEditorWidthInChars(30) - presenter = buildPresenter(explicitHeight: 25, scrollTop: 50) - - expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 3, softWrapped: true} - expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 3, softWrapped: true} - expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 4, softWrapped: false} - - describe ".decorationClasses", -> - it "adds decoration classes to the relevant line number state objects, both initially and when decorations change", -> - marker1 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch') - decoration1 = editor.decorateMarker(marker1, type: 'line-number', class: 'a') - presenter = buildPresenter() - marker2 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch') - decoration2 = editor.decorateMarker(marker2, type: 'line-number', class: 'b') - - expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b'] - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b'] - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b'] - expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() - - expectStateUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x') - expect(marker1.isValid()).toBe false - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() - - expectStateUpdate presenter, -> editor.undo() - expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b'] - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b'] - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b'] - expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() - - expectStateUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]]) - expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a'] - expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a'] - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b'] - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b'] - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b'] - expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() - - expectStateUpdate presenter, -> decoration1.destroy() - expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b'] - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b'] - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b'] - expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() - - expectStateUpdate presenter, -> marker2.destroy() - expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() - - it "honors the 'onlyEmpty' option on line-number decorations", -> - presenter = buildPresenter() - marker = editor.markBufferRange([[4, 0], [6, 1]]) - decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyEmpty: true) - - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() - - expectStateUpdate presenter, -> marker.clearTail() - - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a'] - - it "honors the 'onlyNonEmpty' option on line-number decorations", -> - presenter = buildPresenter() - marker = editor.markBufferRange([[4, 0], [6, 2]]) - decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyNonEmpty: true) - - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a'] - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a'] - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a'] - - expectStateUpdate presenter, -> marker.clearTail() - - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() - - it "honors the 'onlyHead' option on line-number decorations", -> - presenter = buildPresenter() - marker = editor.markBufferRange([[4, 0], [6, 2]]) - decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyHead: true) - - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a'] - - it "does not decorate the last line of a non-empty line-number decoration range if it ends at column 0", -> - presenter = buildPresenter() - marker = editor.markBufferRange([[4, 0], [6, 0]]) - decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a') - - expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a'] - expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a'] - expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() - - it "does not apply line-number decorations to mini editors", -> - editor.setMini(true) - presenter = buildPresenter() - marker = editor.markBufferRange([[0, 0], [0, 0]]) - decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a') - # A mini editor will have no gutters. - expect(getLineNumberGutterState(presenter)).toBeUndefined() - - expectStateUpdate presenter, -> editor.setMini(false) - expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toEqual ['cursor-line', 'cursor-line-no-selection', 'a'] - - expectStateUpdate presenter, -> editor.setMini(true) - expect(getLineNumberGutterState(presenter)).toBeUndefined() - - it "only applies line-number decorations to screen rows that are spanned by their marker when lines are soft-wrapped", -> - editor.setText("a line that wraps, ok") + describe ".lineNumbers[id]", -> + it "contains states for line numbers that are visible on screen", -> + editor.foldBufferRow(4) editor.setSoftWrapped(true) - editor.setEditorWidthInChars(16) - marker = editor.markBufferRange([[0, 0], [0, 2]]) - editor.decorateMarker(marker, type: 'line-number', class: 'a') - presenter = buildPresenter(explicitHeight: 10) + editor.setEditorWidthInChars(50) + presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, tileSize: 2) - expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a' - expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() + expectValues lineNumberStateForScreenRow(presenter, 2), {screenRow: 2, bufferRow: 2, softWrapped: false, top: 0 * 10} + expectValues lineNumberStateForScreenRow(presenter, 3), {screenRow: 3, bufferRow: 3, softWrapped: false, top: 1 * 10} + expectValues lineNumberStateForScreenRow(presenter, 4), {screenRow: 4, bufferRow: 3, softWrapped: true, top: 0 * 10} + expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false, top: 1 * 10} + expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 7, softWrapped: false, top: 0 * 10} + expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 8, softWrapped: false, top: 1 * 10} + expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined() - marker.setBufferRange([[0, 0], [0, Infinity]]) - expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a' - expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toContain 'a' + it "updates when the editor's content changes", -> + editor.foldBufferRow(4) + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(50) + presenter = buildPresenter(explicitHeight: 35, scrollTop: 30, tileSize: 2) - describe ".foldable", -> - it "marks line numbers at the start of a foldable region as foldable", -> - presenter = buildPresenter() - expect(lineNumberStateForScreenRow(presenter, 0).foldable).toBe true - expect(lineNumberStateForScreenRow(presenter, 1).foldable).toBe true - expect(lineNumberStateForScreenRow(presenter, 2).foldable).toBe false - expect(lineNumberStateForScreenRow(presenter, 3).foldable).toBe false - expect(lineNumberStateForScreenRow(presenter, 4).foldable).toBe true - expect(lineNumberStateForScreenRow(presenter, 5).foldable).toBe false + expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() + expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2} + expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} + expectValues lineNumberStateForScreenRow(presenter, 4), {bufferRow: 3} + expectValues lineNumberStateForScreenRow(presenter, 5), {bufferRow: 4} + expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 7} + expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 8} + expectValues lineNumberStateForScreenRow(presenter, 8), {bufferRow: 8} + expectValues lineNumberStateForScreenRow(presenter, 9), {bufferRow: 9} + expect(lineNumberStateForScreenRow(presenter, 10)).toBeUndefined() - it "updates the foldable class on the correct line numbers when the foldable positions change", -> - presenter = buildPresenter() - editor.getBuffer().insert([0, 0], '\n') - expect(lineNumberStateForScreenRow(presenter, 0).foldable).toBe false - expect(lineNumberStateForScreenRow(presenter, 1).foldable).toBe true - expect(lineNumberStateForScreenRow(presenter, 2).foldable).toBe true - expect(lineNumberStateForScreenRow(presenter, 3).foldable).toBe false - expect(lineNumberStateForScreenRow(presenter, 4).foldable).toBe false - expect(lineNumberStateForScreenRow(presenter, 5).foldable).toBe true - expect(lineNumberStateForScreenRow(presenter, 6).foldable).toBe false + expectStateUpdate presenter, -> + editor.getBuffer().insert([3, Infinity], new Array(25).join("x ")) - it "updates the foldable class on a line number that becomes foldable", -> - presenter = buildPresenter() - expect(lineNumberStateForScreenRow(presenter, 11).foldable).toBe false + expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined() + expectValues lineNumberStateForScreenRow(presenter, 2), {bufferRow: 2} + expectValues lineNumberStateForScreenRow(presenter, 3), {bufferRow: 3} + expectValues lineNumberStateForScreenRow(presenter, 4), {bufferRow: 3} + expectValues lineNumberStateForScreenRow(presenter, 5), {bufferRow: 3} + expectValues lineNumberStateForScreenRow(presenter, 6), {bufferRow: 4} + expectValues lineNumberStateForScreenRow(presenter, 7), {bufferRow: 7} + expectValues lineNumberStateForScreenRow(presenter, 8), {bufferRow: 8} + expectValues lineNumberStateForScreenRow(presenter, 9), {bufferRow: 8} + expect(lineNumberStateForScreenRow(presenter, 10)).toBeUndefined() - editor.getBuffer().insert([11, 44], '\n fold me') - expect(lineNumberStateForScreenRow(presenter, 11).foldable).toBe true + it "correctly handles the first screen line being soft-wrapped", -> + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(30) + presenter = buildPresenter(explicitHeight: 25, scrollTop: 50, tileSize: 2) - editor.undo() - expect(lineNumberStateForScreenRow(presenter, 11).foldable).toBe false + expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 3, softWrapped: true} + expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 3, softWrapped: true} + expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 4, softWrapped: false} + + describe ".decorationClasses", -> + it "adds decoration classes to the relevant line number state objects, both initially and when decorations change", -> + marker1 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch') + decoration1 = editor.decorateMarker(marker1, type: 'line-number', class: 'a') + presenter = buildPresenter() + marker2 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch') + decoration2 = editor.decorateMarker(marker2, type: 'line-number', class: 'b') + + expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b'] + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b'] + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b'] + expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() + + expectStateUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x') + expect(marker1.isValid()).toBe false + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() + + expectStateUpdate presenter, -> editor.undo() + expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b'] + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b'] + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b'] + expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() + + expectStateUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]]) + expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a'] + expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a'] + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b'] + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b'] + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b'] + expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() + + expectStateUpdate presenter, -> decoration1.destroy() + expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b'] + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b'] + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b'] + expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() + + expectStateUpdate presenter, -> marker2.destroy() + expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull() + + it "honors the 'onlyEmpty' option on line-number decorations", -> + presenter = buildPresenter() + marker = editor.markBufferRange([[4, 0], [6, 1]]) + decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyEmpty: true) + + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() + + expectStateUpdate presenter, -> marker.clearTail() + + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a'] + + it "honors the 'onlyNonEmpty' option on line-number decorations", -> + presenter = buildPresenter() + marker = editor.markBufferRange([[4, 0], [6, 2]]) + decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyNonEmpty: true) + + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a'] + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a'] + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a'] + + expectStateUpdate presenter, -> marker.clearTail() + + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() + + it "honors the 'onlyHead' option on line-number decorations", -> + presenter = buildPresenter() + marker = editor.markBufferRange([[4, 0], [6, 2]]) + decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyHead: true) + + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull() + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a'] + + it "does not decorate the last line of a non-empty line-number decoration range if it ends at column 0", -> + presenter = buildPresenter() + marker = editor.markBufferRange([[4, 0], [6, 0]]) + decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a') + + expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a'] + expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a'] + expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull() + + it "does not apply line-number decorations to mini editors", -> + editor.setMini(true) + presenter = buildPresenter() + marker = editor.markBufferRange([[0, 0], [0, 0]]) + decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a') + # A mini editor will have no gutters. + expect(getLineNumberGutterState(presenter)).toBeUndefined() + + expectStateUpdate presenter, -> editor.setMini(false) + expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toEqual ['cursor-line', 'cursor-line-no-selection', 'a'] + + expectStateUpdate presenter, -> editor.setMini(true) + expect(getLineNumberGutterState(presenter)).toBeUndefined() + + it "only applies line-number decorations to screen rows that are spanned by their marker when lines are soft-wrapped", -> + editor.setText("a line that wraps, ok") + editor.setSoftWrapped(true) + editor.setEditorWidthInChars(16) + marker = editor.markBufferRange([[0, 0], [0, 2]]) + editor.decorateMarker(marker, type: 'line-number', class: 'a') + presenter = buildPresenter(explicitHeight: 10) + + expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a' + expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull() + + marker.setBufferRange([[0, 0], [0, Infinity]]) + expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a' + expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toContain 'a' + + describe ".foldable", -> + it "marks line numbers at the start of a foldable region as foldable", -> + presenter = buildPresenter() + expect(lineNumberStateForScreenRow(presenter, 0).foldable).toBe true + expect(lineNumberStateForScreenRow(presenter, 1).foldable).toBe true + expect(lineNumberStateForScreenRow(presenter, 2).foldable).toBe false + expect(lineNumberStateForScreenRow(presenter, 3).foldable).toBe false + expect(lineNumberStateForScreenRow(presenter, 4).foldable).toBe true + expect(lineNumberStateForScreenRow(presenter, 5).foldable).toBe false + + it "updates the foldable class on the correct line numbers when the foldable positions change", -> + presenter = buildPresenter() + editor.getBuffer().insert([0, 0], '\n') + expect(lineNumberStateForScreenRow(presenter, 0).foldable).toBe false + expect(lineNumberStateForScreenRow(presenter, 1).foldable).toBe true + expect(lineNumberStateForScreenRow(presenter, 2).foldable).toBe true + expect(lineNumberStateForScreenRow(presenter, 3).foldable).toBe false + expect(lineNumberStateForScreenRow(presenter, 4).foldable).toBe false + expect(lineNumberStateForScreenRow(presenter, 5).foldable).toBe true + expect(lineNumberStateForScreenRow(presenter, 6).foldable).toBe false + + it "updates the foldable class on a line number that becomes foldable", -> + presenter = buildPresenter() + expect(lineNumberStateForScreenRow(presenter, 11).foldable).toBe false + + editor.getBuffer().insert([11, 44], '\n fold me') + expect(lineNumberStateForScreenRow(presenter, 11).foldable).toBe true + + editor.undo() + expect(lineNumberStateForScreenRow(presenter, 11).foldable).toBe false describe "for a gutter description that corresponds to a custom gutter", -> describe ".content", -> diff --git a/src/gutter-container-component.coffee b/src/gutter-container-component.coffee index 92ef5e8b6..09ab43f24 100644 --- a/src/gutter-container-component.coffee +++ b/src/gutter-container-component.coffee @@ -7,7 +7,6 @@ LineNumberGutterComponent = require './line-number-gutter-component' module.exports = class GutterContainerComponent - constructor: ({@onLineNumberGutterMouseDown, @editor}) -> # An array of objects of the form: {name: {String}, component: {Object}} @gutterComponents = [] diff --git a/src/line-number-gutter-component.coffee b/src/line-number-gutter-component.coffee index 7675493de..b6a5b81f6 100644 --- a/src/line-number-gutter-component.coffee +++ b/src/line-number-gutter-component.coffee @@ -1,14 +1,13 @@ -_ = require 'underscore-plus' -{setDimensionsAndBackground} = require './gutter-component-helpers' - +TiledComponent = require './tiled-component' +LineNumbersTileComponent = require './line-numbers-tile-component' WrapperDiv = document.createElement('div') +DummyLineNumberComponent = LineNumbersTileComponent.createDummy() module.exports = -class LineNumberGutterComponent +class LineNumberGutterComponent extends TiledComponent dummyLineNumberNode: null constructor: ({@onMouseDown, @editor, @gutter}) -> - @lineNumberNodesById = {} @visible = true @domNode = atom.views.getView(@gutter) @@ -35,28 +34,33 @@ class LineNumberGutterComponent @domNode.style.removeProperty('display') @visible = true - # `state` is a subset of the TextEditorPresenter state that is specific - # to this line number gutter. - updateSync: (state) -> - @newState = state - @oldState ?= - lineNumbers: {} + buildEmptyState: -> + { + tiles: {} styles: {} + } + getNewState: (state) -> state + + getTilesNode: -> @lineNumbersNode + + beforeUpdateSync: (state) -> @appendDummyLineNumber() unless @dummyLineNumberNode? - setDimensionsAndBackground(@oldState.styles, @newState.styles, @lineNumbersNode) + if @newState.styles.scrollHeight isnt @oldState.styles.scrollHeight + @lineNumbersNode.style.height = @newState.styles.scrollHeight + 'px' + @oldState.scrollHeight = @newState.scrollHeight + + if @newState.styles.backgroundColor isnt @oldState.styles.backgroundColor + @lineNumbersNode.style.backgroundColor = @newState.styles.backgroundColor + @oldState.styles.backgroundColor = @newState.styles.backgroundColor if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits @updateDummyLineNumber() - node.remove() for id, node of @lineNumberNodesById - @oldState = - maxLineNumberDigits: @newState.maxLineNumberDigits - lineNumbers: {} - styles: {} - @lineNumberNodesById = {} + @oldState.styles = {} + @oldState.maxLineNumberDigits = @newState.maxLineNumberDigits - @updateLineNumbers() + buildComponentForTile: (id) -> new LineNumbersTileComponent({id}) ### Section: Private Methods @@ -65,94 +69,14 @@ class LineNumberGutterComponent # This dummy line number element holds the gutter to the appropriate width, # since the real line numbers are absolutely positioned for performance reasons. appendDummyLineNumber: -> - WrapperDiv.innerHTML = @buildLineNumberHTML({bufferRow: -1}) + DummyLineNumberComponent.newState = @newState + WrapperDiv.innerHTML = DummyLineNumberComponent.buildLineNumberHTML({bufferRow: -1}) @dummyLineNumberNode = WrapperDiv.children[0] @lineNumbersNode.appendChild(@dummyLineNumberNode) updateDummyLineNumber: -> - @dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false) - - updateLineNumbers: -> - newLineNumberIds = null - newLineNumbersHTML = null - - for id, lineNumberState of @newState.lineNumbers - if @oldState.lineNumbers.hasOwnProperty(id) - @updateLineNumberNode(id, lineNumberState) - else - newLineNumberIds ?= [] - newLineNumbersHTML ?= "" - newLineNumberIds.push(id) - newLineNumbersHTML += @buildLineNumberHTML(lineNumberState) - @oldState.lineNumbers[id] = _.clone(lineNumberState) - - if newLineNumberIds? - WrapperDiv.innerHTML = newLineNumbersHTML - newLineNumberNodes = _.toArray(WrapperDiv.children) - - node = @lineNumbersNode - for id, i in newLineNumberIds - lineNumberNode = newLineNumberNodes[i] - @lineNumberNodesById[id] = lineNumberNode - node.appendChild(lineNumberNode) - - for id, lineNumberState of @oldState.lineNumbers - unless @newState.lineNumbers.hasOwnProperty(id) - @lineNumberNodesById[id].remove() - delete @lineNumberNodesById[id] - delete @oldState.lineNumbers[id] - - return - - buildLineNumberHTML: (lineNumberState) -> - {screenRow, bufferRow, softWrapped, top, decorationClasses} = lineNumberState - if screenRow? - style = "position: absolute; top: #{top}px;" - else - style = "visibility: hidden;" - className = @buildLineNumberClassName(lineNumberState) - innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped) - - "