mirror of
https://github.com/atom/atom.git
synced 2026-01-24 06:18:03 -05:00
204 lines
5.9 KiB
CoffeeScript
204 lines
5.9 KiB
CoffeeScript
_ = require 'underscore-plus'
|
|
|
|
idCounter = 1
|
|
|
|
module.exports =
|
|
class TokenizedLine
|
|
constructor: ({tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold, @tabLength, @indentLevel}) ->
|
|
@startBufferColumn ?= 0
|
|
@tokens = @breakOutAtomicTokens(tokens)
|
|
@text = @buildText()
|
|
@bufferDelta = @buildBufferDelta()
|
|
|
|
@id = idCounter++
|
|
@markLeadingAndTrailingWhitespaceTokens()
|
|
|
|
buildText: ->
|
|
text = ""
|
|
text += token.value for token in @tokens
|
|
text
|
|
|
|
buildBufferDelta: ->
|
|
delta = 0
|
|
delta += token.bufferDelta for token in @tokens
|
|
delta
|
|
|
|
copy: ->
|
|
new TokenizedLine({@tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold})
|
|
|
|
clipScreenColumn: (column, options={}) ->
|
|
return 0 if @tokens.length == 0
|
|
|
|
{ skipAtomicTokens } = options
|
|
column = Math.min(column, @getMaxScreenColumn())
|
|
|
|
tokenStartColumn = 0
|
|
for token in @tokens
|
|
break if tokenStartColumn + token.screenDelta > column
|
|
tokenStartColumn += token.screenDelta
|
|
|
|
if token.isAtomic and tokenStartColumn < column
|
|
if skipAtomicTokens
|
|
tokenStartColumn + token.screenDelta
|
|
else
|
|
tokenStartColumn
|
|
else
|
|
column
|
|
|
|
screenColumnForBufferColumn: (bufferColumn, options) ->
|
|
bufferColumn = bufferColumn - @startBufferColumn
|
|
screenColumn = 0
|
|
currentBufferColumn = 0
|
|
for token in @tokens
|
|
break if currentBufferColumn > bufferColumn
|
|
screenColumn += token.screenDelta
|
|
currentBufferColumn += token.bufferDelta
|
|
@clipScreenColumn(screenColumn + (bufferColumn - currentBufferColumn))
|
|
|
|
bufferColumnForScreenColumn: (screenColumn, options) ->
|
|
bufferColumn = @startBufferColumn
|
|
currentScreenColumn = 0
|
|
for token in @tokens
|
|
break if currentScreenColumn + token.screenDelta > screenColumn
|
|
bufferColumn += token.bufferDelta
|
|
currentScreenColumn += token.screenDelta
|
|
bufferColumn + (screenColumn - currentScreenColumn)
|
|
|
|
getMaxScreenColumn: ->
|
|
if @fold
|
|
0
|
|
else
|
|
@text.length
|
|
|
|
getMaxBufferColumn: ->
|
|
@startBufferColumn + @bufferDelta
|
|
|
|
softWrapAt: (column) ->
|
|
return [new TokenizedLine([], '', [0, 0], [0, 0]), this] if column == 0
|
|
|
|
rightTokens = new Array(@tokens...)
|
|
leftTokens = []
|
|
leftTextLength = 0
|
|
while leftTextLength < column
|
|
if leftTextLength + rightTokens[0].value.length > column
|
|
rightTokens[0..0] = rightTokens[0].splitAt(column - leftTextLength)
|
|
nextToken = rightTokens.shift()
|
|
leftTextLength += nextToken.value.length
|
|
leftTokens.push nextToken
|
|
|
|
leftFragment = new TokenizedLine(
|
|
tokens: leftTokens
|
|
startBufferColumn: @startBufferColumn
|
|
ruleStack: @ruleStack
|
|
lineEnding: null
|
|
)
|
|
rightFragment = new TokenizedLine(
|
|
tokens: rightTokens
|
|
startBufferColumn: @bufferColumnForScreenColumn(column)
|
|
ruleStack: @ruleStack
|
|
lineEnding: @lineEnding
|
|
)
|
|
[leftFragment, rightFragment]
|
|
|
|
isSoftWrapped: ->
|
|
@lineEnding is null
|
|
|
|
tokenAtBufferColumn: (bufferColumn) ->
|
|
@tokens[@tokenIndexAtBufferColumn(bufferColumn)]
|
|
|
|
tokenIndexAtBufferColumn: (bufferColumn) ->
|
|
delta = 0
|
|
for token, index in @tokens
|
|
delta += token.bufferDelta
|
|
return index if delta > bufferColumn
|
|
index - 1
|
|
|
|
tokenStartColumnForBufferColumn: (bufferColumn) ->
|
|
delta = 0
|
|
for token in @tokens
|
|
nextDelta = delta + token.bufferDelta
|
|
break if nextDelta > bufferColumn
|
|
delta = nextDelta
|
|
delta
|
|
|
|
breakOutAtomicTokens: (inputTokens) ->
|
|
outputTokens = []
|
|
breakOutLeadingSoftTabs = true
|
|
column = @startBufferColumn
|
|
for token in inputTokens
|
|
newTokens = token.breakOutAtomicTokens(@tabLength, breakOutLeadingSoftTabs, column)
|
|
column += newToken.value.length for newToken in newTokens
|
|
outputTokens.push(newTokens...)
|
|
breakOutLeadingSoftTabs = token.isOnlyWhitespace() if breakOutLeadingSoftTabs
|
|
outputTokens
|
|
|
|
markLeadingAndTrailingWhitespaceTokens: ->
|
|
firstNonWhitespacePosition = @text.search(/\S/)
|
|
firstTrailingWhitespacePosition = @text.search(/\s*$/)
|
|
lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0
|
|
position = 0
|
|
for token, i in @tokens
|
|
token.hasLeadingWhitespace = position < firstNonWhitespacePosition
|
|
# Only the *last* segment of a soft-wrapped line can have trailing whitespace
|
|
token.hasTrailingWhitespace = @lineEnding? and (position + token.value.length > firstTrailingWhitespacePosition)
|
|
position += token.value.length
|
|
|
|
isComment: ->
|
|
for token in @tokens
|
|
continue if token.scopes.length is 1
|
|
continue if token.isOnlyWhitespace()
|
|
for scope in token.scopes
|
|
return true if _.contains(scope.split('.'), 'comment')
|
|
break
|
|
false
|
|
|
|
isOnlyWhitespace: ->
|
|
if @text == ''
|
|
true
|
|
else
|
|
for token in @tokens
|
|
return false unless token.isOnlyWhitespace()
|
|
true
|
|
|
|
tokenAtIndex: (index) ->
|
|
@tokens[index]
|
|
|
|
getTokenCount: ->
|
|
@tokens.length
|
|
|
|
bufferColumnForToken: (targetToken) ->
|
|
column = 0
|
|
for token in @tokens
|
|
return column if token is targetToken
|
|
column += token.bufferDelta
|
|
|
|
getScopeTree: ->
|
|
return @scopeTree if @scopeTree?
|
|
|
|
scopeStack = []
|
|
for token in @tokens
|
|
@updateScopeStack(scopeStack, token.scopes)
|
|
_.last(scopeStack).children.push(token)
|
|
|
|
@scopeTree = scopeStack[0]
|
|
@updateScopeStack(scopeStack, [])
|
|
@scopeTree
|
|
|
|
updateScopeStack: (scopeStack, desiredScopes) ->
|
|
# Find a common prefix
|
|
for scope, i in desiredScopes
|
|
break unless scopeStack[i]?.scope is desiredScopes[i]
|
|
|
|
# Pop scopes until we're at the common prefx
|
|
until scopeStack.length is i
|
|
poppedScope = scopeStack.pop()
|
|
_.last(scopeStack)?.children.push(poppedScope)
|
|
|
|
# Push onto common prefix until scopeStack equals desiredScopes
|
|
for j in [i...desiredScopes.length]
|
|
scopeStack.push(new Scope(desiredScopes[j]))
|
|
|
|
class Scope
|
|
constructor: (@scope) ->
|
|
@children = []
|