mirror of
https://github.com/atom/atom.git
synced 2026-02-07 05:05:02 -05:00
108 lines
3.8 KiB
CoffeeScript
108 lines
3.8 KiB
CoffeeScript
_ = require 'underscore'
|
|
ScreenLine = require 'screen-line'
|
|
EventEmitter = require 'event-emitter'
|
|
Token = require 'token'
|
|
Range = require 'range'
|
|
Point = require 'point'
|
|
|
|
module.exports =
|
|
class Highlighter
|
|
@idCounter: 1
|
|
buffer: null
|
|
screenLines: []
|
|
|
|
constructor: (@buffer, @tabText) ->
|
|
@id = @constructor.idCounter++
|
|
@screenLines = @buildScreenLinesForRows('start', 0, @buffer.getLastRow())
|
|
@buffer.on "change.highlighter#{@id}", (e) => @handleBufferChange(e)
|
|
|
|
handleBufferChange: (e) ->
|
|
oldRange = e.oldRange.copy()
|
|
newRange = e.newRange.copy()
|
|
previousState = @screenLines[oldRange.end.row].state # used in spill detection below
|
|
|
|
startState = @screenLines[newRange.start.row - 1]?.state or 'start'
|
|
@screenLines[oldRange.start.row..oldRange.end.row] =
|
|
@buildScreenLinesForRows(startState, newRange.start.row, newRange.end.row)
|
|
|
|
# spill detection
|
|
# compare scanner state of last re-highlighted line with its previous state.
|
|
# if it differs, re-tokenize the next line with the new state and repeat for
|
|
# each line until the line's new state matches the previous state. this covers
|
|
# cases like inserting a /* needing to comment out lines below until we see a */
|
|
for row in [newRange.end.row...@buffer.getLastRow()]
|
|
break if @screenLines[row].state == previousState
|
|
nextRow = row + 1
|
|
previousState = @screenLines[nextRow].state
|
|
@screenLines[nextRow] = @buildScreenLineForRow(@screenLines[row].state, nextRow)
|
|
|
|
# if highlighting spilled beyond the bounds of the textual change, update
|
|
# the pre and post range to reflect area of highlight changes
|
|
if nextRow > newRange.end.row
|
|
oldRange.end.row += (nextRow - newRange.end.row)
|
|
newRange.end.row = nextRow
|
|
endColumn = @buffer.lineForRow(nextRow).length
|
|
newRange.end.column = endColumn
|
|
oldRange.end.column = endColumn
|
|
|
|
@trigger("change", {oldRange, newRange})
|
|
|
|
buildScreenLinesForRows: (startState, startRow, endRow) ->
|
|
state = startState
|
|
for row in [startRow..endRow]
|
|
screenLine = @buildScreenLineForRow(state, row)
|
|
state = screenLine.state
|
|
screenLine
|
|
|
|
buildScreenLineForRow: (state, row) ->
|
|
tokenizer = @buffer.getMode().getTokenizer()
|
|
line = @buffer.lineForRow(row)
|
|
{tokens, state} = tokenizer.getLineTokens(line, state)
|
|
tokenObjects = []
|
|
for tokenProperties in tokens
|
|
token = new Token(tokenProperties)
|
|
tokenObjects.push(token.breakOutTabCharacters(@tabText)...)
|
|
text = _.pluck(tokenObjects, 'value').join('')
|
|
new ScreenLine(tokenObjects, text, [1, 0], [1, 0], { state })
|
|
|
|
screenLineForRow: (row) ->
|
|
@screenLines[row]
|
|
|
|
screenLinesForRows: (startRow, endRow) ->
|
|
@screenLines[startRow..endRow]
|
|
|
|
destroy: ->
|
|
@buffer.off ".highlighter#{@id}"
|
|
|
|
iterateTokensInBufferRange: (bufferRange, iterator) ->
|
|
bufferRange = Range.fromObject(bufferRange)
|
|
{ start, end } = bufferRange
|
|
|
|
keepLooping = true
|
|
stop = -> keepLooping = false
|
|
|
|
for bufferRow in [start.row..end.row]
|
|
bufferColumn = 0
|
|
for token in @screenLines[bufferRow].tokens
|
|
startOfToken = new Point(bufferRow, bufferColumn)
|
|
iterator(token, startOfToken, { stop }) if bufferRange.containsPoint(startOfToken)
|
|
return unless keepLooping
|
|
bufferColumn += token.bufferDelta
|
|
|
|
findClosingBracket: (startBufferPosition) ->
|
|
range = [startBufferPosition, @buffer.getEofPosition()]
|
|
position = null
|
|
depth = 0
|
|
@iterateTokensInBufferRange range, (token, startPosition, { stop }) ->
|
|
if token.type.match /lparen|rparen/
|
|
if token.value == '{'
|
|
depth++
|
|
else if token.value == '}'
|
|
depth--
|
|
if depth == 0
|
|
position = startPosition
|
|
stop()
|
|
position
|
|
|
|
_.extend(Highlighter.prototype, EventEmitter)
|