mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
Add a character width cache based on scopes.
This is slower than the position cache in the best case, but faster in the worst and average case. With this, you can search for a space in find and replace, and still scroll the buffer. In editor.coffee, there are 10,500 spaces. To highlight all of them, the previous cache method took 7 seconds, this takes 2 when the cache is empty, and about 10ms when the entire file is cached.
This commit is contained in:
@@ -10,12 +10,15 @@ $ = require './jquery-extensions'
|
||||
_ = require './underscore-extensions'
|
||||
|
||||
MEASURE_RANGE = document.createRange()
|
||||
TEXT_NODE_FILTER = { acceptNode: -> NodeFilter.FILTER_ACCEPT }
|
||||
NO_SCOPE = ['no-scope']
|
||||
|
||||
# Private: Represents the entire visual pane in Atom.
|
||||
#
|
||||
# The Editor manages the {EditSession}, which manages the file buffers.
|
||||
module.exports =
|
||||
class Editor extends View
|
||||
@characterWidthCache: {}
|
||||
@configDefaults:
|
||||
fontSize: 20
|
||||
showInvisibles: false
|
||||
@@ -96,7 +99,6 @@ class Editor extends View
|
||||
@pendingChanges = []
|
||||
@newCursors = []
|
||||
@newSelections = []
|
||||
@pixelLeftCache = new WeakMap()
|
||||
|
||||
if editSession?
|
||||
@edit(editSession)
|
||||
@@ -969,6 +971,9 @@ class Editor extends View
|
||||
# fontSize - A {Number} indicating the font size in pixels.
|
||||
setFontSize: (fontSize) ->
|
||||
@css('font-size', "#{fontSize}px}")
|
||||
|
||||
@clearCharacterWidthCache()
|
||||
|
||||
if @isOnDom()
|
||||
@redraw()
|
||||
else
|
||||
@@ -985,6 +990,9 @@ class Editor extends View
|
||||
# fontFamily - A {String} identifying the CSS `font-family`,
|
||||
setFontFamily: (fontFamily='') ->
|
||||
@css('font-family', fontFamily)
|
||||
|
||||
@clearCharacterWidthCache()
|
||||
|
||||
@redraw()
|
||||
|
||||
# Gets the font family for the editor.
|
||||
@@ -1366,7 +1374,6 @@ class Editor extends View
|
||||
currentLine = clearLine(currentLine)
|
||||
|
||||
clearLine: (lineElement) =>
|
||||
@pixelLeftCache.delete(lineElement)
|
||||
next = lineElement.nextSibling
|
||||
@renderedLines[0].removeChild(lineElement)
|
||||
next
|
||||
@@ -1545,33 +1552,91 @@ class Editor extends View
|
||||
unless existingLineElement
|
||||
lineElement = @buildLineElementForScreenRow(actualRow)
|
||||
@renderedLines.append(lineElement)
|
||||
left = @positionLeftForLineAndColumn(lineElement, column)
|
||||
left = @positionLeftForLineAndColumn(lineElement, actualRow, column)
|
||||
unless existingLineElement
|
||||
@renderedLines[0].removeChild(lineElement)
|
||||
{ top: row * @lineHeight, left }
|
||||
|
||||
positionLeftForLineAndColumn: (lineElement, column) ->
|
||||
lineCache = @pixelLeftCache.get(lineElement)
|
||||
@pixelLeftCache.set(lineElement, lineCache = {}) unless lineCache?
|
||||
positionLeftForLineAndColumn: (lineElement, screenRow, column) ->
|
||||
return 0 if column == 0
|
||||
|
||||
return lineCache[column] if lineCache[column]?
|
||||
bufferRow = @bufferRowsForScreenRows(screenRow)[0] ? screenRow
|
||||
tokenizedLine = @activeEditSession.displayBuffer.tokenizedBuffer.tokenizedLines[bufferRow]
|
||||
|
||||
delta = 0
|
||||
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, acceptNode: -> NodeFilter.FILTER_ACCEPT)
|
||||
while textNode = iterator.nextNode()
|
||||
nextDelta = delta + textNode.textContent.length
|
||||
if nextDelta >= column
|
||||
offset = column - delta
|
||||
break
|
||||
delta = nextDelta
|
||||
left = 0
|
||||
index = 0
|
||||
for token in tokenizedLine.tokens
|
||||
for char in token.value
|
||||
return left if index >= column
|
||||
|
||||
MEASURE_RANGE.setEnd(textNode, offset)
|
||||
MEASURE_RANGE.collapse()
|
||||
left = MEASURE_RANGE.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
|
||||
val = @checkCharacterWidthCache(token.scopes, char)
|
||||
if val?
|
||||
left += val
|
||||
else
|
||||
return @measureToColumn(lineElement, tokenizedLine, column)
|
||||
|
||||
lineCache[column] = left
|
||||
index++
|
||||
left
|
||||
|
||||
scopesForColumn: (tokenizedLine, column) ->
|
||||
index = 0
|
||||
for token in tokenizedLine.tokens
|
||||
for char in token.value
|
||||
return token.scopes if index == column
|
||||
index++
|
||||
null
|
||||
|
||||
measureToColumn: (lineElement, tokenizedLine, column) ->
|
||||
left = oldLeft = index = 0
|
||||
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TEXT_NODE_FILTER)
|
||||
|
||||
returnLeft = null
|
||||
|
||||
while textNode = iterator.nextNode()
|
||||
content = textNode.textContent
|
||||
|
||||
for char, i in content
|
||||
|
||||
# Dont return right away, finish caching the whole line
|
||||
returnLeft = left if index == column
|
||||
oldLeft = left
|
||||
|
||||
scopes = @scopesForColumn(tokenizedLine, index)
|
||||
cachedVal = @checkCharacterWidthCache(scopes, char)
|
||||
|
||||
if cachedVal?
|
||||
left = oldLeft + cachedVal
|
||||
else
|
||||
# i + 1 to measure to the end of the current character
|
||||
MEASURE_RANGE.setEnd(textNode, i + 1)
|
||||
MEASURE_RANGE.collapse()
|
||||
left = MEASURE_RANGE.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
|
||||
|
||||
@setCharacterWidthCache(scopes, char, left - oldLeft) if scopes?
|
||||
|
||||
index++
|
||||
|
||||
returnLeft ? left
|
||||
|
||||
checkCharacterWidthCache: (scopes, char) ->
|
||||
scopes ?= NO_SCOPE
|
||||
obj = Editor.characterWidthCache
|
||||
for scope in scopes
|
||||
obj = obj[scope]
|
||||
return null unless obj?
|
||||
obj[char]
|
||||
|
||||
setCharacterWidthCache: (scopes, char, val) ->
|
||||
scopes ?= NO_SCOPE
|
||||
obj = Editor.characterWidthCache
|
||||
for scope in scopes
|
||||
obj[scope] ?= {}
|
||||
obj = obj[scope]
|
||||
obj[char] = val
|
||||
|
||||
clearCharacterWidthCache: ->
|
||||
Editor.characterWidthCache = {}
|
||||
|
||||
pixelOffsetForScreenPosition: (position) ->
|
||||
{top, left} = @pixelPositionForScreenPosition(position)
|
||||
offset = @renderedLines.offset()
|
||||
|
||||
Reference in New Issue
Block a user