mirror of
https://github.com/atom/atom.git
synced 2026-01-24 14:28:14 -05:00
@@ -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
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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)
|
||||
|
||||
"<div class=\"#{className}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||
|
||||
buildLineNumberInnerHTML: (bufferRow, softWrapped) ->
|
||||
{maxLineNumberDigits} = @newState
|
||||
|
||||
if softWrapped
|
||||
lineNumber = "•"
|
||||
else
|
||||
lineNumber = (bufferRow + 1).toString()
|
||||
|
||||
padding = _.multiplyString(' ', maxLineNumberDigits - lineNumber.length)
|
||||
iconHTML = '<div class="icon-right"></div>'
|
||||
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
|
||||
|
||||
134
src/line-numbers-tile-component.coffee
Normal file
134
src/line-numbers-tile-component.coffee
Normal file
@@ -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)
|
||||
|
||||
"<div class=\"#{className}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||
|
||||
buildLineNumberInnerHTML: (bufferRow, softWrapped) ->
|
||||
{maxLineNumberDigits} = @newState
|
||||
|
||||
if softWrapped
|
||||
lineNumber = "•"
|
||||
else
|
||||
lineNumber = (bufferRow + 1).toString()
|
||||
|
||||
padding = _.multiplyString(' ', maxLineNumberDigits - lineNumber.length)
|
||||
iconHTML = '<div class="icon-right"></div>'
|
||||
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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)"
|
||||
@@ -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?
|
||||
|
||||
@@ -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')
|
||||
|
||||
51
src/tiled-component.coffee
Normal file
51
src/tiled-component.coffee
Normal file
@@ -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]
|
||||
Reference in New Issue
Block a user