mirror of
https://github.com/atom/atom.git
synced 2026-01-29 08:48:17 -05:00
Add LinesYardstick
...and create a MockLineNodesProvider for testing purposes
This commit is contained in:
58
spec/lines-yardstick-spec.coffee
Normal file
58
spec/lines-yardstick-spec.coffee
Normal 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)
|
||||
34
spec/mock-line-nodes-provider.coffee
Normal file
34
spec/mock-line-nodes-provider.coffee
Normal 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
|
||||
82
src/lines-yardstick.coffee
Normal file
82
src/lines-yardstick.coffee
Normal 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
|
||||
Reference in New Issue
Block a user