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) - - "
#{innerHTML}
" - - buildLineNumberInnerHTML: (bufferRow, softWrapped) -> - {maxLineNumberDigits} = @newState - - if softWrapped - lineNumber = "•" - else - lineNumber = (bufferRow + 1).toString() - - padding = _.multiplyString(' ', maxLineNumberDigits - lineNumber.length) - iconHTML = '
' - padding + lineNumber + iconHTML - - updateLineNumberNode: (lineNumberId, newLineNumberState) -> - oldLineNumberState = @oldState.lineNumbers[lineNumberId] - node = @lineNumberNodesById[lineNumberId] - - unless oldLineNumberState.foldable is newLineNumberState.foldable and _.isEqual(oldLineNumberState.decorationClasses, newLineNumberState.decorationClasses) - node.className = @buildLineNumberClassName(newLineNumberState) - oldLineNumberState.foldable = newLineNumberState.foldable - oldLineNumberState.decorationClasses = _.clone(newLineNumberState.decorationClasses) - - unless oldLineNumberState.top is newLineNumberState.top - node.style.top = newLineNumberState.top + 'px' - node.dataset.screenRow = newLineNumberState.screenRow - oldLineNumberState.top = newLineNumberState.top - oldLineNumberState.screenRow = newLineNumberState.screenRow - - buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) -> - className = "line-number line-number-#{bufferRow}" - className += " " + decorationClasses.join(' ') if decorationClasses? - className += " foldable" if foldable and not softWrapped - className - - lineNumberNodeForScreenRow: (screenRow) -> - for id, lineNumberState of @oldState.lineNumbers - if lineNumberState.screenRow is screenRow - return @lineNumberNodesById[id] - null + DummyLineNumberComponent.newState = @newState + @dummyLineNumberNode.innerHTML = DummyLineNumberComponent.buildLineNumberInnerHTML(0, false) onMouseDown: (event) => {target} = event diff --git a/src/line-numbers-tile-component.coffee b/src/line-numbers-tile-component.coffee new file mode 100644 index 000000000..cf58c54f3 --- /dev/null +++ b/src/line-numbers-tile-component.coffee @@ -0,0 +1,134 @@ +_ = require 'underscore-plus' +WrapperDiv = document.createElement('div') + +module.exports = +class LineNumbersTileComponent + @createDummy: -> + new LineNumbersTileComponent({id: -1}) + + constructor: ({@id}) -> + @lineNumberNodesById = {} + @domNode = document.createElement("div") + @domNode.classList.add("tile") + @domNode.style.position = "absolute" + @domNode.style.display = "block" + @domNode.style.top = 0 # Cover the space occupied by a dummy lineNumber + + getDomNode: -> + @domNode + + updateSync: (state) -> + @newState = state + unless @oldState + @oldState = {tiles: {}, styles: {}} + @oldState.tiles[@id] = {lineNumbers: {}} + + @newTileState = @newState.tiles[@id] + @oldTileState = @oldState.tiles[@id] + + if @newTileState.display isnt @oldTileState.display + @domNode.style.display = @newTileState.display + @oldTileState.display = @newTileState.display + + if @newState.styles.backgroundColor isnt @oldState.styles.backgroundColor + @domNode.style.backgroundColor = @newState.styles.backgroundColor + @oldState.styles.backgroundColor = @newState.styles.backgroundColor + + if @newTileState.height isnt @oldTileState.height + @domNode.style.height = @newTileState.height + 'px' + @oldTileState.height = @newTileState.height + + if @newTileState.top isnt @oldTileState.top + @domNode.style['-webkit-transform'] = "translate3d(0, #{@newTileState.top}px, 0px)" + @oldTileState.top = @newTileState.top + + if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits + node.remove() for id, node of @lineNumberNodesById + @oldState.tiles[@id] = {lineNumbers: {}} + @oldTileState = @oldState.tiles[@id] + @lineNumberNodesById = {} + @oldState.maxLineNumberDigits = @newState.maxLineNumberDigits + + @updateLineNumbers() + + updateLineNumbers: -> + newLineNumberIds = null + newLineNumbersHTML = null + + for id, lineNumberState of @oldTileState.lineNumbers + unless @newTileState.lineNumbers.hasOwnProperty(id) + @lineNumberNodesById[id].remove() + delete @lineNumberNodesById[id] + delete @oldTileState.lineNumbers[id] + + for id, lineNumberState of @newTileState.lineNumbers + if @oldTileState.lineNumbers.hasOwnProperty(id) + @updateLineNumberNode(id, lineNumberState) + else + newLineNumberIds ?= [] + newLineNumbersHTML ?= "" + newLineNumberIds.push(id) + newLineNumbersHTML += @buildLineNumberHTML(lineNumberState) + @oldTileState.lineNumbers[id] = _.clone(lineNumberState) + + if newLineNumberIds? + WrapperDiv.innerHTML = newLineNumbersHTML + newLineNumberNodes = _.toArray(WrapperDiv.children) + + node = @domNode + for id, i in newLineNumberIds + lineNumberNode = newLineNumberNodes[i] + @lineNumberNodesById[id] = lineNumberNode + node.appendChild(lineNumberNode) + + 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) + + "
#{innerHTML}
" + + buildLineNumberInnerHTML: (bufferRow, softWrapped) -> + {maxLineNumberDigits} = @newState + + if softWrapped + lineNumber = "•" + else + lineNumber = (bufferRow + 1).toString() + + padding = _.multiplyString(' ', maxLineNumberDigits - lineNumber.length) + iconHTML = '
' + padding + lineNumber + iconHTML + + updateLineNumberNode: (lineNumberId, newLineNumberState) -> + oldLineNumberState = @oldTileState.lineNumbers[lineNumberId] + node = @lineNumberNodesById[lineNumberId] + + unless oldLineNumberState.foldable is newLineNumberState.foldable and _.isEqual(oldLineNumberState.decorationClasses, newLineNumberState.decorationClasses) + node.className = @buildLineNumberClassName(newLineNumberState) + oldLineNumberState.foldable = newLineNumberState.foldable + oldLineNumberState.decorationClasses = _.clone(newLineNumberState.decorationClasses) + + unless oldLineNumberState.top is newLineNumberState.top + node.style.top = newLineNumberState.top + 'px' + node.dataset.screenRow = newLineNumberState.screenRow + oldLineNumberState.top = newLineNumberState.top + oldLineNumberState.screenRow = newLineNumberState.screenRow + + buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) -> + className = "line-number line-number-#{bufferRow}" + className += " " + decorationClasses.join(' ') if decorationClasses? + className += " foldable" if foldable and not softWrapped + className + + lineNumberNodeForScreenRow: (screenRow) -> + for id, lineNumberState of @oldTileState.lineNumbers + if lineNumberState.screenRow is screenRow + return @lineNumberNodesById[id] + null diff --git a/src/lines-component.coffee b/src/lines-component.coffee index f8b6579a5..70a428b57 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -1,26 +1,20 @@ {$$} = require 'space-pen' CursorsComponent = require './cursors-component' -TileComponent = require './tile-component' +LinesTileComponent = require './lines-tile-component' +TiledComponent = require './tiled-component' DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] -cloneObject = (object) -> - clone = {} - clone[key] = value for key, value of object - clone - module.exports = -class LinesComponent +class LinesComponent extends TiledComponent placeholderTextDiv: null constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) -> - @tileComponentsByTileId = {} - @domNode = document.createElement('div') @domNode.classList.add('lines') - @cursorsComponent = new CursorsComponent(@presenter) + @cursorsComponent = new CursorsComponent @domNode.appendChild(@cursorsComponent.getDomNode()) if @useShadowDOM @@ -31,10 +25,10 @@ class LinesComponent getDomNode: -> @domNode - updateSync: (state) -> - @newState = state.content - @oldState ?= {tiles: {}} + shouldRecreateAllTilesOnUpdate: -> + @oldState.indentGuidesVisible isnt @newState.indentGuidesVisible + beforeUpdateSync: (state) -> if @newState.scrollHeight isnt @oldState.scrollHeight @domNode.style.height = @newState.scrollHeight + 'px' @oldState.scrollHeight = @newState.scrollHeight @@ -43,6 +37,7 @@ class LinesComponent @domNode.style.backgroundColor = @newState.backgroundColor @oldState.backgroundColor = @newState.backgroundColor + afterUpdateSync: (state) -> if @newState.placeholderText isnt @oldState.placeholderText @placeholderTextDiv?.remove() if @newState.placeholderText? @@ -51,46 +46,23 @@ class LinesComponent @placeholderTextDiv.textContent = @newState.placeholderText @domNode.appendChild(@placeholderTextDiv) - @removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible - @updateTileNodes() - if @newState.width isnt @oldState.width @domNode.style.width = @newState.width + 'px' + @oldState.width = @newState.width @cursorsComponent.updateSync(state) @oldState.indentGuidesVisible = @newState.indentGuidesVisible - @oldState.scrollWidth = @newState.scrollWidth - @oldState.width = @newState.width - removeTileNodes: -> - @removeTileNode(id) for id of @oldState.tiles - return + buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter}) - removeTileNode: (id) -> - node = @tileComponentsByTileId[id].getDomNode() + buildEmptyState: -> + {tiles: {}} - node.remove() - delete @tileComponentsByTileId[id] - delete @oldState.tiles[id] + getNewState: (state) -> + state.content - updateTileNodes: -> - for id of @oldState.tiles - unless @newState.tiles.hasOwnProperty(id) - @removeTileNode(id) - - for id, tileState of @newState.tiles - if @oldState.tiles.hasOwnProperty(id) - tileComponent = @tileComponentsByTileId[id] - else - tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter}) - - @domNode.appendChild(tileComponent.getDomNode()) - @oldState.tiles[id] = cloneObject(tileState) - - tileComponent.updateSync(@newState) - - return + getTilesNode: -> @domNode measureLineHeightAndDefaultCharWidth: -> @domNode.appendChild(DummyLineNode) @@ -109,18 +81,13 @@ class LinesComponent measureCharactersInNewLines: -> @presenter.batchCharacterMeasurement => - for id, component of @tileComponentsByTileId + for id, component of @componentsByTileId component.measureCharactersInNewLines() return clearScopedCharWidths: -> - for id, component of @tileComponentsByTileId + for id, component of @componentsByTileId component.clearMeasurements() @presenter.clearScopedCharacterWidths() - - lineNodeForScreenRow: (screenRow) -> - tile = @presenter.tileForRow(screenRow) - - @tileComponentsByTileId[tile]?.lineNodeForScreenRow(screenRow) diff --git a/src/tile-component.coffee b/src/lines-tile-component.coffee similarity index 99% rename from src/tile-component.coffee rename to src/lines-tile-component.coffee index 99dfc6ced..5fdadf212 100644 --- a/src/tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -13,7 +13,7 @@ cloneObject = (object) -> clone module.exports = -class TileComponent +class LinesTileComponent constructor: ({@presenter, @id}) -> @tokenIterator = new TokenIterator @measuredLines = new Set @@ -25,7 +25,7 @@ class TileComponent @domNode.style.position = "absolute" @domNode.style.display = "block" - @highlightsComponent = new HighlightsComponent(@presenter) + @highlightsComponent = new HighlightsComponent @domNode.appendChild(@highlightsComponent.getDomNode()) getDomNode: -> @@ -54,6 +54,7 @@ class TileComponent if @newState.width isnt @oldState.width @domNode.style.width = @newState.width + 'px' + @oldTileState.width = @newTileState.width if @newTileState.top isnt @oldTileState.top or @newTileState.left isnt @oldTileState.left @domNode.style['-webkit-transform'] = "translate3d(#{@newTileState.left}px, #{@newTileState.top}px, 0px)" diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index b8fe39976..45591c195 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -729,9 +729,18 @@ class TextEditorComponent consolidateSelections: (e) -> e.abortKeyBinding() unless @editor.consolidateSelections() - lineNodeForScreenRow: (screenRow) -> @linesComponent.lineNodeForScreenRow(screenRow) + lineNodeForScreenRow: (screenRow) -> + tileRow = @presenter.tileForRow(screenRow) + tileComponent = @linesComponent.getComponentForTile(tileRow) - lineNumberNodeForScreenRow: (screenRow) -> @gutterContainerComponent.getLineNumberGutterComponent().lineNumberNodeForScreenRow(screenRow) + tileComponent?.lineNodeForScreenRow(screenRow) + + lineNumberNodeForScreenRow: (screenRow) -> + tileRow = @presenter.tileForRow(screenRow) + gutterComponent = @gutterContainerComponent.getLineNumberGutterComponent() + tileComponent = gutterComponent.getComponentForTile(tileRow) + + tileComponent?.lineNumberNodeForScreenRow(screenRow) screenRowForNode: (node) -> while node? diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 9fa923255..933ddd4aa 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -80,11 +80,10 @@ class TextEditorPresenter @updateHiddenInputState() if @shouldUpdateHiddenInputState @updateContentState() if @shouldUpdateContentState @updateDecorations() if @shouldUpdateDecorations - @updateTilesState() if @shouldUpdateTilesState + @updateTilesState() if @shouldUpdateLinesState or @shouldUpdateLineNumbersState @updateCursorsState() if @shouldUpdateCursorsState @updateOverlaysState() if @shouldUpdateOverlaysState @updateLineNumberGutterState() if @shouldUpdateLineNumberGutterState - @updateLineNumbersState() if @shouldUpdateLineNumbersState @updateGutterOrderState() if @shouldUpdateGutterOrderState @updateCustomGutterDecorationState() if @shouldUpdateCustomGutterDecorationState @updating = false @@ -102,7 +101,7 @@ class TextEditorPresenter @shouldUpdateHiddenInputState = false @shouldUpdateContentState = false @shouldUpdateDecorations = false - @shouldUpdateTilesState = false + @shouldUpdateLinesState = false @shouldUpdateCursorsState = false @shouldUpdateOverlaysState = false @shouldUpdateLineNumberGutterState = false @@ -121,7 +120,7 @@ class TextEditorPresenter @shouldUpdateContentState = true @shouldUpdateDecorations = true @shouldUpdateCursorsState = true - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @shouldUpdateLineNumberGutterState = true @shouldUpdateLineNumbersState = true @shouldUpdateGutterOrderState = true @@ -129,7 +128,7 @@ class TextEditorPresenter @emitDidUpdateState() @model.onDidUpdateMarkers => - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @shouldUpdateLineNumbersState = true @shouldUpdateDecorations = true @shouldUpdateOverlaysState = true @@ -145,7 +144,7 @@ class TextEditorPresenter @shouldUpdateScrollbarsState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @shouldUpdateLineNumberGutterState = true @shouldUpdateLineNumbersState = true @shouldUpdateGutterOrderState = true @@ -228,11 +227,14 @@ class TextEditorPresenter @sharedGutterStyles = {} @customGutterDecorations = {} @lineNumberGutter = - lineNumbers: {} + tiles: {} @updateState() updateState: -> + @shouldUpdateLinesState = true + @shouldUpdateLineNumbersState = true + @updateContentDimensions() @updateScrollbarDimensions() @updateStartRow() @@ -250,7 +252,6 @@ class TextEditorPresenter @updateCursorsState() @updateOverlaysState() @updateLineNumberGutterState() - @updateLineNumbersState() @updateCommonGutterState() @updateGutterOrderState() @updateCustomGutterDecorationState() @@ -339,7 +340,13 @@ class TextEditorPresenter tile.display = "block" tile.highlights ?= {} - @updateLinesState(tile, startRow, endRow) + gutterTile = @lineNumberGutter.tiles[startRow] ?= {} + gutterTile.top = startRow * @lineHeight - @scrollTop + gutterTile.height = @tileSize * @lineHeight + gutterTile.display = "block" + + @updateLinesState(tile, startRow, endRow) if @shouldUpdateLinesState + @updateLineNumbersState(gutterTile, startRow, endRow) if @shouldUpdateLineNumbersState visibleTiles[startRow] = true @@ -347,6 +354,7 @@ class TextEditorPresenter mouseWheelTile = @tileForRow(@mouseWheelScreenRow) unless visibleTiles[mouseWheelTile]? + @lineNumberGutter.tiles[mouseWheelTile].display = "none" @state.content.tiles[mouseWheelTile].display = "none" visibleTiles[mouseWheelTile] = true @@ -354,6 +362,7 @@ class TextEditorPresenter continue if visibleTiles.hasOwnProperty(id) delete @state.content.tiles[id] + delete @lineNumberGutter.tiles[id] updateLinesState: (tileState, startRow, endRow) -> tileState.lines ?= {} @@ -562,21 +571,20 @@ class TextEditorPresenter isVisible = isVisible and @showLineNumbers isVisible - updateLineNumbersState: -> - return unless @startRow? and @endRow? and @lineHeight? - + updateLineNumbersState: (tileState, startRow, endRow) -> + tileState.lineNumbers ?= {} visibleLineNumberIds = {} - if @startRow > 0 - rowBeforeStartRow = @startRow - 1 + if startRow > 0 + rowBeforeStartRow = startRow - 1 lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow) wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow) else lastBufferRow = null wrapCount = 0 - if @endRow > @startRow - for bufferRow, i in @model.bufferRowsForScreenRows(@startRow, @endRow - 1) + if endRow > startRow + for bufferRow, i in @model.bufferRowsForScreenRows(startRow, endRow - 1) if bufferRow is lastBufferRow wrapCount++ id = bufferRow + '-' + wrapCount @@ -587,23 +595,16 @@ class TextEditorPresenter lastBufferRow = bufferRow softWrapped = false - screenRow = @startRow + i - top = screenRow * @lineHeight + screenRow = startRow + i + top = (screenRow - startRow) * @lineHeight decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) - @lineNumberGutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} + tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} visibleLineNumberIds[id] = true - if @mouseWheelScreenRow? - bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow) - wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow) - id = bufferRow - id += '-' + wrapCount if wrapCount > 0 - visibleLineNumberIds[id] = true - - for id of @lineNumberGutter.lineNumbers - delete @lineNumberGutter.lineNumbers[id] unless visibleLineNumberIds[id] + for id of tileState.lineNumbers + delete tileState.lineNumbers[id] unless visibleLineNumberIds[id] return @@ -797,7 +798,7 @@ class TextEditorPresenter @shouldUpdateVerticalScrollState = true @shouldUpdateHiddenInputState = true @shouldUpdateDecorations = true - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @shouldUpdateCursorsState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -820,7 +821,7 @@ class TextEditorPresenter @state.content.scrollingVertically = false if @mouseWheelScreenRow? @mouseWheelScreenRow = null - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -837,7 +838,7 @@ class TextEditorPresenter @shouldUpdateCursorsState = true @shouldUpdateOverlaysState = true @shouldUpdateDecorations = true - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @emitDidUpdateState() @@ -885,7 +886,7 @@ class TextEditorPresenter @shouldUpdateVerticalScrollState = true @shouldUpdateScrollbarsState = true @shouldUpdateDecorations = true - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @shouldUpdateCursorsState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -913,7 +914,7 @@ class TextEditorPresenter @shouldUpdateScrollbarsState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @shouldUpdateCursorsState = true unless oldContentFrameWidth? @emitDidUpdateState() @@ -979,7 +980,7 @@ class TextEditorPresenter @shouldUpdateScrollbarsState = true @shouldUpdateHiddenInputState = true @shouldUpdateDecorations = true - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @shouldUpdateCursorsState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true @@ -1031,7 +1032,7 @@ class TextEditorPresenter @shouldUpdateHiddenInputState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true @shouldUpdateCursorsState = true @shouldUpdateOverlaysState = true @@ -1119,7 +1120,7 @@ class TextEditorPresenter @shouldUpdateDecorations = true if decoration.isType('line') or decoration.isType('gutter') if decoration.isType('line') or Decoration.isType(oldProperties, 'line') - @shouldUpdateTilesState = true + @shouldUpdateLinesState = true if decoration.isType('line-number') or Decoration.isType(oldProperties, 'line-number') @shouldUpdateLineNumbersState = true if (decoration.isType('gutter') and not decoration.isType('line-number')) or @@ -1132,7 +1133,7 @@ class TextEditorPresenter didDestroyDecoration: (decoration) -> @shouldUpdateDecorations = true if decoration.isType('line') or decoration.isType('gutter') - @shouldUpdateTilesState = true if decoration.isType('line') + @shouldUpdateLinesState = true if decoration.isType('line') if decoration.isType('line-number') @shouldUpdateLineNumbersState = true else if decoration.isType('gutter') @@ -1147,7 +1148,7 @@ class TextEditorPresenter if decoration.isType('line') or decoration.isType('gutter') @shouldUpdateDecorations = true - @shouldUpdateTilesState = true if decoration.isType('line') + @shouldUpdateLinesState = true if decoration.isType('line') if decoration.isType('line-number') @shouldUpdateLineNumbersState = true else if decoration.isType('gutter') diff --git a/src/tiled-component.coffee b/src/tiled-component.coffee new file mode 100644 index 000000000..33719dda5 --- /dev/null +++ b/src/tiled-component.coffee @@ -0,0 +1,51 @@ +cloneObject = (object) -> + clone = {} + clone[key] = value for key, value of object + clone + +module.exports = +class TiledComponent + updateSync: (state) -> + @newState = @getNewState(state) + @oldState ?= @buildEmptyState() + + @beforeUpdateSync?(state) + + @removeTileNodes() if @shouldRecreateAllTilesOnUpdate?() + @updateTileNodes() + + @afterUpdateSync?(state) + + removeTileNodes: -> + @removeTileNode(tileRow) for tileRow of @oldState.tiles + return + + removeTileNode: (tileRow) -> + node = @componentsByTileId[tileRow].getDomNode() + + node.remove() + delete @componentsByTileId[tileRow] + delete @oldState.tiles[tileRow] + + updateTileNodes: -> + @componentsByTileId ?= {} + + for tileRow of @oldState.tiles + unless @newState.tiles.hasOwnProperty(tileRow) + @removeTileNode(tileRow) + + for tileRow, tileState of @newState.tiles + if @oldState.tiles.hasOwnProperty(tileRow) + component = @componentsByTileId[tileRow] + else + component = @componentsByTileId[tileRow] = @buildComponentForTile(tileRow) + + @getTilesNode().appendChild(component.getDomNode()) + @oldState.tiles[tileRow] = cloneObject(tileState) + + component.updateSync(@newState) + + return + + getComponentForTile: (tileRow) -> + @componentsByTileId[tileRow]