mirror of
https://github.com/atom/atom.git
synced 2026-01-28 16:28:09 -05:00
870 lines
26 KiB
JavaScript
870 lines
26 KiB
JavaScript
const _ = require('underscore-plus')
|
|
const {CompositeDisposable, Emitter} = require('event-kit')
|
|
const {Point, Range} = require('text-buffer')
|
|
const TokenizedLine = require('./tokenized-line')
|
|
const TokenIterator = require('./token-iterator')
|
|
const ScopeDescriptor = require('./scope-descriptor')
|
|
const NullGrammar = require('./null-grammar')
|
|
const {OnigRegExp} = require('oniguruma')
|
|
const {toFirstMateScopeId, fromFirstMateScopeId} = require('./first-mate-helpers')
|
|
const {selectorMatchesAnyScope} = require('./selectors')
|
|
|
|
const NON_WHITESPACE_REGEX = /\S/
|
|
|
|
let nextId = 0
|
|
const prefixedScopes = new Map()
|
|
|
|
class TextMateLanguageMode {
|
|
constructor (params) {
|
|
this.emitter = new Emitter()
|
|
this.disposables = new CompositeDisposable()
|
|
this.tokenIterator = new TokenIterator(this)
|
|
this.regexesByPattern = {}
|
|
|
|
this.alive = true
|
|
this.tokenizationStarted = false
|
|
this.id = params.id != null ? params.id : nextId++
|
|
this.buffer = params.buffer
|
|
this.largeFileMode = params.largeFileMode
|
|
this.config = params.config
|
|
this.largeFileMode = params.largeFileMode != null
|
|
? params.largeFileMode
|
|
: this.buffer.buffer.getLength() >= 2 * 1024 * 1024
|
|
|
|
this.grammar = params.grammar || NullGrammar
|
|
this.rootScopeDescriptor = new ScopeDescriptor({scopes: [this.grammar.scopeName]})
|
|
this.disposables.add(this.grammar.onDidUpdate(() => this.retokenizeLines()))
|
|
this.retokenizeLines()
|
|
}
|
|
|
|
destroy () {
|
|
if (!this.alive) return
|
|
this.alive = false
|
|
this.disposables.dispose()
|
|
this.tokenizedLines.length = 0
|
|
}
|
|
|
|
isAlive () {
|
|
return this.alive
|
|
}
|
|
|
|
isDestroyed () {
|
|
return !this.alive
|
|
}
|
|
|
|
getGrammar () {
|
|
return this.grammar
|
|
}
|
|
|
|
getLanguageId () {
|
|
return this.grammar.scopeName
|
|
}
|
|
|
|
getNonWordCharacters (position) {
|
|
const scope = this.scopeDescriptorForPosition(position)
|
|
return this.config.get('editor.nonWordCharacters', {scope})
|
|
}
|
|
|
|
/*
|
|
Section - auto-indent
|
|
*/
|
|
|
|
// Get the suggested indentation level for an existing line in the buffer.
|
|
//
|
|
// * bufferRow - A {Number} indicating the buffer row
|
|
//
|
|
// Returns a {Number}.
|
|
suggestedIndentForBufferRow (bufferRow, tabLength, options) {
|
|
const line = this.buffer.lineForRow(bufferRow)
|
|
const tokenizedLine = this.tokenizedLineForRow(bufferRow)
|
|
const iterator = tokenizedLine.getTokenIterator()
|
|
iterator.next()
|
|
const scopeDescriptor = new ScopeDescriptor({scopes: iterator.getScopes()})
|
|
return this._suggestedIndentForLineWithScopeAtBufferRow(
|
|
bufferRow,
|
|
line,
|
|
scopeDescriptor,
|
|
tabLength,
|
|
options
|
|
)
|
|
}
|
|
|
|
// Get the suggested indentation level for a given line of text, if it were inserted at the given
|
|
// row in the buffer.
|
|
//
|
|
// * bufferRow - A {Number} indicating the buffer row
|
|
//
|
|
// Returns a {Number}.
|
|
suggestedIndentForLineAtBufferRow (bufferRow, line, tabLength) {
|
|
const tokenizedLine = this.buildTokenizedLineForRowWithText(bufferRow, line)
|
|
const iterator = tokenizedLine.getTokenIterator()
|
|
iterator.next()
|
|
const scopeDescriptor = new ScopeDescriptor({scopes: iterator.getScopes()})
|
|
return this._suggestedIndentForLineWithScopeAtBufferRow(
|
|
bufferRow,
|
|
line,
|
|
scopeDescriptor,
|
|
tabLength
|
|
)
|
|
}
|
|
|
|
// Get the suggested indentation level for a line in the buffer on which the user is currently
|
|
// typing. This may return a different result from {::suggestedIndentForBufferRow} in order
|
|
// to avoid unexpected changes in indentation. It may also return undefined if no change should
|
|
// be made.
|
|
//
|
|
// * bufferRow - The row {Number}
|
|
//
|
|
// Returns a {Number}.
|
|
suggestedIndentForEditedBufferRow (bufferRow, tabLength) {
|
|
const line = this.buffer.lineForRow(bufferRow)
|
|
const currentIndentLevel = this.indentLevelForLine(line, tabLength)
|
|
if (currentIndentLevel === 0) return
|
|
|
|
const scopeDescriptor = this.scopeDescriptorForPosition(new Point(bufferRow, 0))
|
|
const decreaseIndentRegex = this.decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
|
|
if (!decreaseIndentRegex) return
|
|
|
|
if (!decreaseIndentRegex.testSync(line)) return
|
|
|
|
const precedingRow = this.buffer.previousNonBlankRow(bufferRow)
|
|
if (precedingRow == null) return
|
|
|
|
const precedingLine = this.buffer.lineForRow(precedingRow)
|
|
let desiredIndentLevel = this.indentLevelForLine(precedingLine, tabLength)
|
|
|
|
const increaseIndentRegex = this.increaseIndentRegexForScopeDescriptor(scopeDescriptor)
|
|
if (increaseIndentRegex) {
|
|
if (!increaseIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1
|
|
}
|
|
|
|
const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor(scopeDescriptor)
|
|
if (decreaseNextIndentRegex) {
|
|
if (decreaseNextIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1
|
|
}
|
|
|
|
if (desiredIndentLevel < 0) return 0
|
|
if (desiredIndentLevel >= currentIndentLevel) return
|
|
return desiredIndentLevel
|
|
}
|
|
|
|
_suggestedIndentForLineWithScopeAtBufferRow (bufferRow, line, scopeDescriptor, tabLength, options) {
|
|
const increaseIndentRegex = this.increaseIndentRegexForScopeDescriptor(scopeDescriptor)
|
|
const decreaseIndentRegex = this.decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
|
|
const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor(scopeDescriptor)
|
|
|
|
let precedingRow
|
|
if (!options || options.skipBlankLines !== false) {
|
|
precedingRow = this.buffer.previousNonBlankRow(bufferRow)
|
|
if (precedingRow == null) return 0
|
|
} else {
|
|
precedingRow = bufferRow - 1
|
|
if (precedingRow < 0) return 0
|
|
}
|
|
|
|
const precedingLine = this.buffer.lineForRow(precedingRow)
|
|
let desiredIndentLevel = this.indentLevelForLine(precedingLine, tabLength)
|
|
if (!increaseIndentRegex) return desiredIndentLevel
|
|
|
|
if (!this.isRowCommented(precedingRow)) {
|
|
if (increaseIndentRegex && increaseIndentRegex.testSync(precedingLine)) desiredIndentLevel += 1
|
|
if (decreaseNextIndentRegex && decreaseNextIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1
|
|
}
|
|
|
|
if (!this.buffer.isRowBlank(precedingRow)) {
|
|
if (decreaseIndentRegex && decreaseIndentRegex.testSync(line)) desiredIndentLevel -= 1
|
|
}
|
|
|
|
return Math.max(desiredIndentLevel, 0)
|
|
}
|
|
|
|
/*
|
|
Section - Comments
|
|
*/
|
|
|
|
commentStringsForPosition (position) {
|
|
const scope = this.scopeDescriptorForPosition(position)
|
|
const commentStartEntries = this.config.getAll('editor.commentStart', {scope})
|
|
const commentEndEntries = this.config.getAll('editor.commentEnd', {scope})
|
|
const commentStartEntry = commentStartEntries[0]
|
|
const commentEndEntry = commentEndEntries.find((entry) => {
|
|
return entry.scopeSelector === commentStartEntry.scopeSelector
|
|
})
|
|
return {
|
|
commentStartString: commentStartEntry && commentStartEntry.value,
|
|
commentEndString: commentEndEntry && commentEndEntry.value
|
|
}
|
|
}
|
|
|
|
/*
|
|
Section - Syntax Highlighting
|
|
*/
|
|
|
|
buildHighlightIterator () {
|
|
return new TextMateHighlightIterator(this)
|
|
}
|
|
|
|
classNameForScopeId (id) {
|
|
const scope = this.grammar.scopeForId(toFirstMateScopeId(id))
|
|
if (scope) {
|
|
let prefixedScope = prefixedScopes.get(scope)
|
|
if (prefixedScope) {
|
|
return prefixedScope
|
|
} else {
|
|
prefixedScope = `syntax--${scope.replace(/\./g, ' syntax--')}`
|
|
prefixedScopes.set(scope, prefixedScope)
|
|
return prefixedScope
|
|
}
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
getInvalidatedRanges () {
|
|
return []
|
|
}
|
|
|
|
onDidChangeHighlighting (fn) {
|
|
return this.emitter.on('did-change-highlighting', fn)
|
|
}
|
|
|
|
onDidTokenize (callback) {
|
|
return this.emitter.on('did-tokenize', callback)
|
|
}
|
|
|
|
getGrammarSelectionContent () {
|
|
return this.buffer.getTextInRange([[0, 0], [10, 0]])
|
|
}
|
|
|
|
updateForInjection (grammar) {
|
|
if (!grammar.injectionSelector) return
|
|
for (const tokenizedLine of this.tokenizedLines) {
|
|
if (tokenizedLine) {
|
|
for (let token of tokenizedLine.tokens) {
|
|
if (grammar.injectionSelector.matches(token.scopes)) {
|
|
this.retokenizeLines()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
retokenizeLines () {
|
|
if (!this.alive) return
|
|
this.fullyTokenized = false
|
|
this.tokenizedLines = new Array(this.buffer.getLineCount())
|
|
this.invalidRows = []
|
|
if (this.largeFileMode || this.grammar.name === 'Null Grammar') {
|
|
this.markTokenizationComplete()
|
|
} else {
|
|
this.invalidateRow(0)
|
|
}
|
|
}
|
|
|
|
startTokenizing () {
|
|
this.tokenizationStarted = true
|
|
if (this.grammar.name !== 'Null Grammar' && !this.largeFileMode) {
|
|
this.tokenizeInBackground()
|
|
}
|
|
}
|
|
|
|
tokenizeInBackground () {
|
|
if (!this.tokenizationStarted || this.pendingChunk || !this.alive) return
|
|
|
|
this.pendingChunk = true
|
|
_.defer(() => {
|
|
this.pendingChunk = false
|
|
if (this.isAlive() && this.buffer.isAlive()) this.tokenizeNextChunk()
|
|
})
|
|
}
|
|
|
|
tokenizeNextChunk () {
|
|
let rowsRemaining = this.chunkSize
|
|
|
|
while (this.firstInvalidRow() != null && rowsRemaining > 0) {
|
|
var endRow, filledRegion
|
|
const startRow = this.invalidRows.shift()
|
|
const lastRow = this.buffer.getLastRow()
|
|
if (startRow > lastRow) continue
|
|
|
|
let row = startRow
|
|
while (true) {
|
|
const previousStack = this.stackForRow(row)
|
|
this.tokenizedLines[row] = this.buildTokenizedLineForRow(row, this.stackForRow(row - 1), this.openScopesForRow(row))
|
|
if (--rowsRemaining === 0) {
|
|
filledRegion = false
|
|
endRow = row
|
|
break
|
|
}
|
|
if (row === lastRow || _.isEqual(this.stackForRow(row), previousStack)) {
|
|
filledRegion = true
|
|
endRow = row
|
|
break
|
|
}
|
|
row++
|
|
}
|
|
|
|
this.validateRow(endRow)
|
|
if (!filledRegion) this.invalidateRow(endRow + 1)
|
|
|
|
this.emitter.emit('did-change-highlighting', Range(Point(startRow, 0), Point(endRow + 1, 0)))
|
|
}
|
|
|
|
if (this.firstInvalidRow() != null) {
|
|
this.tokenizeInBackground()
|
|
} else {
|
|
this.markTokenizationComplete()
|
|
}
|
|
}
|
|
|
|
markTokenizationComplete () {
|
|
if (!this.fullyTokenized) {
|
|
this.emitter.emit('did-tokenize')
|
|
}
|
|
this.fullyTokenized = true
|
|
}
|
|
|
|
firstInvalidRow () {
|
|
return this.invalidRows[0]
|
|
}
|
|
|
|
validateRow (row) {
|
|
while (this.invalidRows[0] <= row) this.invalidRows.shift()
|
|
}
|
|
|
|
invalidateRow (row) {
|
|
this.invalidRows.push(row)
|
|
this.invalidRows.sort((a, b) => a - b)
|
|
this.tokenizeInBackground()
|
|
}
|
|
|
|
updateInvalidRows (start, end, delta) {
|
|
this.invalidRows = this.invalidRows.map((row) => {
|
|
if (row < start) {
|
|
return row
|
|
} else if (start <= row && row <= end) {
|
|
return end + delta + 1
|
|
} else if (row > end) {
|
|
return row + delta
|
|
}
|
|
})
|
|
}
|
|
|
|
bufferDidChange (e) {
|
|
this.changeCount = this.buffer.changeCount
|
|
|
|
const {oldRange, newRange} = e
|
|
const start = oldRange.start.row
|
|
const end = oldRange.end.row
|
|
const delta = newRange.end.row - oldRange.end.row
|
|
const oldLineCount = (oldRange.end.row - oldRange.start.row) + 1
|
|
const newLineCount = (newRange.end.row - newRange.start.row) + 1
|
|
|
|
this.updateInvalidRows(start, end, delta)
|
|
const previousEndStack = this.stackForRow(end) // used in spill detection below
|
|
if (this.largeFileMode || (this.grammar.name === 'Null Grammar')) {
|
|
_.spliceWithArray(this.tokenizedLines, start, oldLineCount, new Array(newLineCount))
|
|
} else {
|
|
const newTokenizedLines = this.buildTokenizedLinesForRows(start, end + delta, this.stackForRow(start - 1), this.openScopesForRow(start))
|
|
_.spliceWithArray(this.tokenizedLines, start, oldLineCount, newTokenizedLines)
|
|
const newEndStack = this.stackForRow(end + delta)
|
|
if (newEndStack && !_.isEqual(newEndStack, previousEndStack)) {
|
|
this.invalidateRow(end + delta + 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
bufferDidFinishTransaction () {}
|
|
|
|
isFoldableAtRow (row) {
|
|
return this.endRowForFoldAtRow(row, 1, true) != null
|
|
}
|
|
|
|
buildTokenizedLinesForRows (startRow, endRow, startingStack, startingopenScopes) {
|
|
let ruleStack = startingStack
|
|
let openScopes = startingopenScopes
|
|
const stopTokenizingAt = startRow + this.chunkSize
|
|
const tokenizedLines = []
|
|
for (let row = startRow, end = endRow; row <= end; row++) {
|
|
let tokenizedLine
|
|
if ((ruleStack || (row === 0)) && row < stopTokenizingAt) {
|
|
tokenizedLine = this.buildTokenizedLineForRow(row, ruleStack, openScopes)
|
|
ruleStack = tokenizedLine.ruleStack
|
|
openScopes = this.scopesFromTags(openScopes, tokenizedLine.tags)
|
|
}
|
|
tokenizedLines.push(tokenizedLine)
|
|
}
|
|
|
|
if (endRow >= stopTokenizingAt) {
|
|
this.invalidateRow(stopTokenizingAt)
|
|
this.tokenizeInBackground()
|
|
}
|
|
|
|
return tokenizedLines
|
|
}
|
|
|
|
buildTokenizedLineForRow (row, ruleStack, openScopes) {
|
|
return this.buildTokenizedLineForRowWithText(row, this.buffer.lineForRow(row), ruleStack, openScopes)
|
|
}
|
|
|
|
buildTokenizedLineForRowWithText (row, text, currentRuleStack = this.stackForRow(row - 1), openScopes = this.openScopesForRow(row)) {
|
|
const lineEnding = this.buffer.lineEndingForRow(row)
|
|
const {tags, ruleStack} = this.grammar.tokenizeLine(text, currentRuleStack, row === 0, false)
|
|
return new TokenizedLine({
|
|
openScopes,
|
|
text,
|
|
tags,
|
|
ruleStack,
|
|
lineEnding,
|
|
tokenIterator: this.tokenIterator,
|
|
grammar: this.grammar
|
|
})
|
|
}
|
|
|
|
tokenizedLineForRow (bufferRow) {
|
|
if (bufferRow >= 0 && bufferRow <= this.buffer.getLastRow()) {
|
|
const tokenizedLine = this.tokenizedLines[bufferRow]
|
|
if (tokenizedLine) {
|
|
return tokenizedLine
|
|
} else {
|
|
const text = this.buffer.lineForRow(bufferRow)
|
|
const lineEnding = this.buffer.lineEndingForRow(bufferRow)
|
|
const tags = [
|
|
this.grammar.startIdForScope(this.grammar.scopeName),
|
|
text.length,
|
|
this.grammar.endIdForScope(this.grammar.scopeName)
|
|
]
|
|
this.tokenizedLines[bufferRow] = new TokenizedLine({
|
|
openScopes: [],
|
|
text,
|
|
tags,
|
|
lineEnding,
|
|
tokenIterator: this.tokenIterator,
|
|
grammar: this.grammar
|
|
})
|
|
return this.tokenizedLines[bufferRow]
|
|
}
|
|
}
|
|
}
|
|
|
|
tokenizedLinesForRows (startRow, endRow) {
|
|
const result = []
|
|
for (let row = startRow, end = endRow; row <= end; row++) {
|
|
result.push(this.tokenizedLineForRow(row))
|
|
}
|
|
return result
|
|
}
|
|
|
|
stackForRow (bufferRow) {
|
|
return this.tokenizedLines[bufferRow] && this.tokenizedLines[bufferRow].ruleStack
|
|
}
|
|
|
|
openScopesForRow (bufferRow) {
|
|
const precedingLine = this.tokenizedLines[bufferRow - 1]
|
|
if (precedingLine) {
|
|
return this.scopesFromTags(precedingLine.openScopes, precedingLine.tags)
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
scopesFromTags (startingScopes, tags) {
|
|
const scopes = startingScopes.slice()
|
|
for (const tag of tags) {
|
|
if (tag < 0) {
|
|
if (tag % 2 === -1) {
|
|
scopes.push(tag)
|
|
} else {
|
|
const matchingStartTag = tag + 1
|
|
while (true) {
|
|
if (scopes.pop() === matchingStartTag) break
|
|
if (scopes.length === 0) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return scopes
|
|
}
|
|
|
|
indentLevelForLine (line, tabLength) {
|
|
let indentLength = 0
|
|
for (let i = 0, {length} = line; i < length; i++) {
|
|
const char = line[i]
|
|
if (char === '\t') {
|
|
indentLength += tabLength - (indentLength % tabLength)
|
|
} else if (char === ' ') {
|
|
indentLength++
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return indentLength / tabLength
|
|
}
|
|
|
|
scopeDescriptorForPosition (position) {
|
|
let scopes
|
|
const {row, column} = this.buffer.clipPosition(Point.fromObject(position))
|
|
|
|
const iterator = this.tokenizedLineForRow(row).getTokenIterator()
|
|
while (iterator.next()) {
|
|
if (iterator.getBufferEnd() > column) {
|
|
scopes = iterator.getScopes()
|
|
break
|
|
}
|
|
}
|
|
|
|
// rebuild scope of last token if we iterated off the end
|
|
if (!scopes) {
|
|
scopes = iterator.getScopes()
|
|
scopes.push(...iterator.getScopeEnds().reverse())
|
|
}
|
|
|
|
return new ScopeDescriptor({scopes})
|
|
}
|
|
|
|
tokenForPosition (position) {
|
|
const {row, column} = Point.fromObject(position)
|
|
return this.tokenizedLineForRow(row).tokenAtBufferColumn(column)
|
|
}
|
|
|
|
tokenStartPositionForPosition (position) {
|
|
let {row, column} = Point.fromObject(position)
|
|
column = this.tokenizedLineForRow(row).tokenStartColumnForBufferColumn(column)
|
|
return new Point(row, column)
|
|
}
|
|
|
|
bufferRangeForScopeAtPosition (selector, position) {
|
|
let endColumn, tag, tokenIndex
|
|
position = Point.fromObject(position)
|
|
|
|
const {openScopes, tags} = this.tokenizedLineForRow(position.row)
|
|
const scopes = openScopes.map(tag => this.grammar.scopeForId(tag))
|
|
|
|
let startColumn = 0
|
|
for (tokenIndex = 0; tokenIndex < tags.length; tokenIndex++) {
|
|
tag = tags[tokenIndex]
|
|
if (tag < 0) {
|
|
if ((tag % 2) === -1) {
|
|
scopes.push(this.grammar.scopeForId(tag))
|
|
} else {
|
|
scopes.pop()
|
|
}
|
|
} else {
|
|
endColumn = startColumn + tag
|
|
if (endColumn >= position.column) {
|
|
break
|
|
} else {
|
|
startColumn = endColumn
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!selectorMatchesAnyScope(selector, scopes)) return
|
|
|
|
const startScopes = scopes.slice()
|
|
for (let startTokenIndex = tokenIndex - 1; startTokenIndex >= 0; startTokenIndex--) {
|
|
tag = tags[startTokenIndex]
|
|
if (tag < 0) {
|
|
if ((tag % 2) === -1) {
|
|
startScopes.pop()
|
|
} else {
|
|
startScopes.push(this.grammar.scopeForId(tag))
|
|
}
|
|
} else {
|
|
if (!selectorMatchesAnyScope(selector, startScopes)) { break }
|
|
startColumn -= tag
|
|
}
|
|
}
|
|
|
|
const endScopes = scopes.slice()
|
|
for (let endTokenIndex = tokenIndex + 1, end = tags.length; endTokenIndex < end; endTokenIndex++) {
|
|
tag = tags[endTokenIndex]
|
|
if (tag < 0) {
|
|
if ((tag % 2) === -1) {
|
|
endScopes.push(this.grammar.scopeForId(tag))
|
|
} else {
|
|
endScopes.pop()
|
|
}
|
|
} else {
|
|
if (!selectorMatchesAnyScope(selector, endScopes)) { break }
|
|
endColumn += tag
|
|
}
|
|
}
|
|
|
|
return new Range(new Point(position.row, startColumn), new Point(position.row, endColumn))
|
|
}
|
|
|
|
isRowCommented (row) {
|
|
return this.tokenizedLines[row] && this.tokenizedLines[row].isComment()
|
|
}
|
|
|
|
getFoldableRangeContainingPoint (point, tabLength) {
|
|
if (point.column >= this.buffer.lineLengthForRow(point.row)) {
|
|
const endRow = this.endRowForFoldAtRow(point.row, tabLength)
|
|
if (endRow != null) {
|
|
return Range(Point(point.row, Infinity), Point(endRow, Infinity))
|
|
}
|
|
}
|
|
|
|
for (let row = point.row - 1; row >= 0; row--) {
|
|
const endRow = this.endRowForFoldAtRow(row, tabLength)
|
|
if (endRow != null && endRow >= point.row) {
|
|
return Range(Point(row, Infinity), Point(endRow, Infinity))
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
getFoldableRangesAtIndentLevel (indentLevel, tabLength) {
|
|
const result = []
|
|
let row = 0
|
|
const lineCount = this.buffer.getLineCount()
|
|
while (row < lineCount) {
|
|
if (this.indentLevelForLine(this.buffer.lineForRow(row), tabLength) === indentLevel) {
|
|
const endRow = this.endRowForFoldAtRow(row, tabLength)
|
|
if (endRow != null) {
|
|
result.push(Range(Point(row, Infinity), Point(endRow, Infinity)))
|
|
row = endRow + 1
|
|
continue
|
|
}
|
|
}
|
|
row++
|
|
}
|
|
return result
|
|
}
|
|
|
|
getFoldableRanges (tabLength) {
|
|
const result = []
|
|
let row = 0
|
|
const lineCount = this.buffer.getLineCount()
|
|
while (row < lineCount) {
|
|
const endRow = this.endRowForFoldAtRow(row, tabLength)
|
|
if (endRow != null) {
|
|
result.push(Range(Point(row, Infinity), Point(endRow, Infinity)))
|
|
}
|
|
row++
|
|
}
|
|
return result
|
|
}
|
|
|
|
endRowForFoldAtRow (row, tabLength, existenceOnly = false) {
|
|
if (this.isRowCommented(row)) {
|
|
return this.endRowForCommentFoldAtRow(row, existenceOnly)
|
|
} else {
|
|
return this.endRowForCodeFoldAtRow(row, tabLength, existenceOnly)
|
|
}
|
|
}
|
|
|
|
endRowForCommentFoldAtRow (row, existenceOnly) {
|
|
if (this.isRowCommented(row - 1)) return
|
|
|
|
let endRow
|
|
for (let nextRow = row + 1, end = this.buffer.getLineCount(); nextRow < end; nextRow++) {
|
|
if (!this.isRowCommented(nextRow)) break
|
|
endRow = nextRow
|
|
if (existenceOnly) break
|
|
}
|
|
|
|
return endRow
|
|
}
|
|
|
|
endRowForCodeFoldAtRow (row, tabLength, existenceOnly) {
|
|
let foldEndRow
|
|
const line = this.buffer.lineForRow(row)
|
|
if (!NON_WHITESPACE_REGEX.test(line)) return
|
|
const startIndentLevel = this.indentLevelForLine(line, tabLength)
|
|
const scopeDescriptor = this.scopeDescriptorForPosition([row, 0])
|
|
const foldEndRegex = this.foldEndRegexForScopeDescriptor(scopeDescriptor)
|
|
for (let nextRow = row + 1, end = this.buffer.getLineCount(); nextRow < end; nextRow++) {
|
|
const line = this.buffer.lineForRow(nextRow)
|
|
if (!NON_WHITESPACE_REGEX.test(line)) continue
|
|
const indentation = this.indentLevelForLine(line, tabLength)
|
|
if (indentation < startIndentLevel) {
|
|
break
|
|
} else if (indentation === startIndentLevel) {
|
|
if (foldEndRegex && foldEndRegex.searchSync(line)) foldEndRow = nextRow
|
|
break
|
|
}
|
|
foldEndRow = nextRow
|
|
if (existenceOnly) break
|
|
}
|
|
return foldEndRow
|
|
}
|
|
|
|
increaseIndentRegexForScopeDescriptor (scope) {
|
|
return this.regexForPattern(this.config.get('editor.increaseIndentPattern', {scope}))
|
|
}
|
|
|
|
decreaseIndentRegexForScopeDescriptor (scope) {
|
|
return this.regexForPattern(this.config.get('editor.decreaseIndentPattern', {scope}))
|
|
}
|
|
|
|
decreaseNextIndentRegexForScopeDescriptor (scope) {
|
|
return this.regexForPattern(this.config.get('editor.decreaseNextIndentPattern', {scope}))
|
|
}
|
|
|
|
foldEndRegexForScopeDescriptor (scope) {
|
|
return this.regexForPattern(this.config.get('editor.foldEndPattern', {scope}))
|
|
}
|
|
|
|
regexForPattern (pattern) {
|
|
if (pattern) {
|
|
if (!this.regexesByPattern[pattern]) {
|
|
this.regexesByPattern[pattern] = new OnigRegExp(pattern)
|
|
}
|
|
return this.regexesByPattern[pattern]
|
|
}
|
|
}
|
|
|
|
logLines (start = 0, end = this.buffer.getLastRow()) {
|
|
for (let row = start; row <= end; row++) {
|
|
const line = this.tokenizedLines[row].text
|
|
console.log(row, line, line.length)
|
|
}
|
|
}
|
|
}
|
|
|
|
TextMateLanguageMode.prototype.chunkSize = 50
|
|
|
|
class TextMateHighlightIterator {
|
|
constructor (languageMode) {
|
|
this.languageMode = languageMode
|
|
this.openScopeIds = null
|
|
this.closeScopeIds = null
|
|
}
|
|
|
|
seek (position) {
|
|
this.openScopeIds = []
|
|
this.closeScopeIds = []
|
|
this.tagIndex = null
|
|
|
|
const currentLine = this.languageMode.tokenizedLineForRow(position.row)
|
|
this.currentLineTags = currentLine.tags
|
|
this.currentLineLength = currentLine.text.length
|
|
const containingScopeIds = currentLine.openScopes.map((id) => fromFirstMateScopeId(id))
|
|
|
|
let currentColumn = 0
|
|
for (let index = 0; index < this.currentLineTags.length; index++) {
|
|
const tag = this.currentLineTags[index]
|
|
if (tag >= 0) {
|
|
if (currentColumn >= position.column) {
|
|
this.tagIndex = index
|
|
break
|
|
} else {
|
|
currentColumn += tag
|
|
while (this.closeScopeIds.length > 0) {
|
|
this.closeScopeIds.shift()
|
|
containingScopeIds.pop()
|
|
}
|
|
while (this.openScopeIds.length > 0) {
|
|
const openTag = this.openScopeIds.shift()
|
|
containingScopeIds.push(openTag)
|
|
}
|
|
}
|
|
} else {
|
|
const scopeId = fromFirstMateScopeId(tag)
|
|
if ((tag & 1) === 0) {
|
|
if (this.openScopeIds.length > 0) {
|
|
if (currentColumn >= position.column) {
|
|
this.tagIndex = index
|
|
break
|
|
} else {
|
|
while (this.closeScopeIds.length > 0) {
|
|
this.closeScopeIds.shift()
|
|
containingScopeIds.pop()
|
|
}
|
|
while (this.openScopeIds.length > 0) {
|
|
const openTag = this.openScopeIds.shift()
|
|
containingScopeIds.push(openTag)
|
|
}
|
|
}
|
|
}
|
|
this.closeScopeIds.push(scopeId)
|
|
} else {
|
|
this.openScopeIds.push(scopeId)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.tagIndex == null) {
|
|
this.tagIndex = this.currentLineTags.length
|
|
}
|
|
this.position = Point(position.row, Math.min(this.currentLineLength, currentColumn))
|
|
return containingScopeIds
|
|
}
|
|
|
|
moveToSuccessor () {
|
|
this.openScopeIds = []
|
|
this.closeScopeIds = []
|
|
while (true) {
|
|
if (this.tagIndex === this.currentLineTags.length) {
|
|
if (this.isAtTagBoundary()) {
|
|
break
|
|
} else if (!this.moveToNextLine()) {
|
|
return false
|
|
}
|
|
} else {
|
|
const tag = this.currentLineTags[this.tagIndex]
|
|
if (tag >= 0) {
|
|
if (this.isAtTagBoundary()) {
|
|
break
|
|
} else {
|
|
this.position = Point(this.position.row, Math.min(
|
|
this.currentLineLength,
|
|
this.position.column + this.currentLineTags[this.tagIndex]
|
|
))
|
|
}
|
|
} else {
|
|
const scopeId = fromFirstMateScopeId(tag)
|
|
if ((tag & 1) === 0) {
|
|
if (this.openScopeIds.length > 0) {
|
|
break
|
|
} else {
|
|
this.closeScopeIds.push(scopeId)
|
|
}
|
|
} else {
|
|
this.openScopeIds.push(scopeId)
|
|
}
|
|
}
|
|
this.tagIndex++
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
getPosition () {
|
|
return this.position
|
|
}
|
|
|
|
getCloseScopeIds () {
|
|
return this.closeScopeIds.slice()
|
|
}
|
|
|
|
getOpenScopeIds () {
|
|
return this.openScopeIds.slice()
|
|
}
|
|
|
|
moveToNextLine () {
|
|
this.position = Point(this.position.row + 1, 0)
|
|
const tokenizedLine = this.languageMode.tokenizedLineForRow(this.position.row)
|
|
if (tokenizedLine == null) {
|
|
return false
|
|
} else {
|
|
this.currentLineTags = tokenizedLine.tags
|
|
this.currentLineLength = tokenizedLine.text.length
|
|
this.tagIndex = 0
|
|
return true
|
|
}
|
|
}
|
|
|
|
isAtTagBoundary () {
|
|
return this.closeScopeIds.length > 0 || this.openScopeIds.length > 0
|
|
}
|
|
}
|
|
|
|
TextMateLanguageMode.TextMateHighlightIterator = TextMateHighlightIterator
|
|
module.exports = TextMateLanguageMode
|