Add LinesYardstick

...and create a MockLineNodesProvider for testing purposes
This commit is contained in:
Antonio Scandurra
2015-09-15 18:41:41 +02:00
parent 398fb1f62d
commit 29846d0a51
3 changed files with 174 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
LinesYardstick = require '../src/lines-yardstick'
MockLineNodesProvider = require './mock-line-nodes-provider'
describe "LinesYardstick", ->
[editor, mockLineNodesProvider, builtLineNodes, linesYardstick] = []
beforeEach ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
waitsForPromise ->
atom.project.open('sample.js').then (o) -> editor = o
runs ->
mockLineNodesProvider = new MockLineNodesProvider(editor)
linesYardstick = new LinesYardstick(editor, mockLineNodesProvider)
afterEach ->
mockLineNodesProvider.dispose()
it "converts screen positions to pixel positions", ->
mockLineNodesProvider.setDefaultFont("14px monospace")
conversionTable = [
[[0, 0], {left: 0, top: editor.getLineHeightInPixels() * 0}]
[[0, 3], {left: 24, top: editor.getLineHeightInPixels() * 0}]
[[0, 4], {left: 32, top: editor.getLineHeightInPixels() * 0}]
[[0, 5], {left: 40, top: editor.getLineHeightInPixels() * 0}]
[[1, 0], {left: 0, top: editor.getLineHeightInPixels() * 1}]
[[1, 1], {left: 0, top: editor.getLineHeightInPixels() * 1}]
[[1, 6], {left: 48, top: editor.getLineHeightInPixels() * 1}]
[[1, Infinity], {left: 240, top: editor.getLineHeightInPixels() * 1}]
]
for [point, position] in conversionTable
expect(
linesYardstick.pixelPositionForScreenPosition(point)
).toEqual(position)
mockLineNodesProvider.setFontForScopes(
["source.js", "storage.modifier.js"], "16px monospace"
)
conversionTable = [
[[0, 0], {left: 0, top: editor.getLineHeightInPixels() * 0}]
[[0, 3], {left: 30, top: editor.getLineHeightInPixels() * 0}]
[[0, 4], {left: 38, top: editor.getLineHeightInPixels() * 0}]
[[0, 5], {left: 46, top: editor.getLineHeightInPixels() * 0}]
[[1, 0], {left: 0, top: editor.getLineHeightInPixels() * 1}]
[[1, 1], {left: 0, top: editor.getLineHeightInPixels() * 1}]
[[1, 6], {left: 54, top: editor.getLineHeightInPixels() * 1}]
[[1, Infinity], {left: 246, top: editor.getLineHeightInPixels() * 1}]
]
for [point, position] in conversionTable
expect(
linesYardstick.pixelPositionForScreenPosition(point)
).toEqual(position)

View File

@@ -0,0 +1,34 @@
TokenIterator = require '../src/token-iterator'
module.exports =
class MockLineNodesProvider
constructor: (@editor) ->
@defaultFont = ""
@fontsByScopes = {}
@tokenIterator = new TokenIterator
@builtLineNodes = []
dispose: ->
node.remove() for node in @builtLineNodes
setFontForScopes: (scopes, font) -> @fontsByScopes[scopes] = font
setDefaultFont: (font) -> @defaultFont = font
lineNodeForScreenRow: (screenRow) ->
lineNode = document.createElement("div")
lineNode.style.whiteSpace = "pre"
lineState = @editor.tokenizedLineForScreenRow(screenRow)
@tokenIterator.reset(lineState)
while @tokenIterator.next()
font = @fontsByScopes[@tokenIterator.getScopes()] or @defaultFont
span = document.createElement("span")
span.style.font = font
span.textContent = @tokenIterator.getText()
lineNode.innerHTML += span.outerHTML
@builtLineNodes.push(lineNode)
document.body.appendChild(lineNode)
lineNode

View File

@@ -0,0 +1,82 @@
TokenIterator = require './token-iterator'
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
{Point} = require 'text-buffer'
module.exports =
class LinesYardstick
constructor: (@model, @lineNodesProvider) ->
@tokenIterator = new TokenIterator
@rangeForMeasurement = document.createRange()
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = @model.clipScreenPosition(screenPosition) if clip
targetRow = screenPosition.row
targetColumn = screenPosition.column
baseCharacterWidth = @baseCharacterWidth
top = targetRow * @model.getLineHeightInPixels()
left = @leftPixelPositionForScreenPosition(targetRow, targetColumn)
{top, left}
leftPixelPositionForScreenPosition: (row, column) ->
lineNode = @lineNodesProvider.lineNodeForScreenRow(row)
tokenizedLine = @model.tokenizedLineForScreenRow(row)
iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter)
charIndex = 0
@tokenIterator.reset(tokenizedLine)
while @tokenIterator.next()
text = @tokenIterator.getText()
textIndex = 0
while textIndex < text.length
if @tokenIterator.isPairedCharacter()
char = text
charLength = 2
textIndex += 2
else
char = text[textIndex]
charLength = 1
textIndex++
continue if char is '\0'
unless textNode?
textNode = iterator.nextNode()
textNodeLength = textNode.textContent.length
textNodeIndex = 0
nextTextNodeIndex = textNodeLength
while nextTextNodeIndex <= charIndex
textNode = iterator.nextNode()
textNodeLength = textNode.textContent.length
textNodeIndex = nextTextNodeIndex
nextTextNodeIndex = textNodeIndex + textNodeLength
if charIndex is column
indexWithinToken = charIndex - textNodeIndex
return @leftPixelPositionForCharInTextNode(textNode, indexWithinToken)
charIndex += charLength
if textNode?
@leftPixelPositionForCharInTextNode(textNode, textNode.textContent.length)
else
0
leftPixelPositionForCharInTextNode: (textNode, charIndex) ->
@rangeForMeasurement.setEnd(textNode, textNode.textContent.length)
if charIndex is 0
@rangeForMeasurement.setStart(textNode, 0)
@rangeForMeasurement.getBoundingClientRect().left
else if charIndex is textNode.textContent.length
@rangeForMeasurement.setStart(textNode, 0)
@rangeForMeasurement.getBoundingClientRect().right
else
@rangeForMeasurement.setStart(textNode, charIndex)
@rangeForMeasurement.getBoundingClientRect().left