mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
1010 lines
30 KiB
JavaScript
1010 lines
30 KiB
JavaScript
const Parser = require('tree-sitter')
|
|
const {Point, Range} = require('text-buffer')
|
|
const {Patch} = require('superstring')
|
|
const {Emitter, Disposable} = require('event-kit')
|
|
const ScopeDescriptor = require('./scope-descriptor')
|
|
const TokenizedLine = require('./tokenized-line')
|
|
const TextMateLanguageMode = require('./text-mate-language-mode')
|
|
|
|
let nextId = 0
|
|
const MAX_RANGE = new Range(Point.ZERO, Point.INFINITY).freeze()
|
|
const PARSER_POOL = []
|
|
|
|
class TreeSitterLanguageMode {
|
|
static _patchSyntaxNode () {
|
|
if (!Parser.SyntaxNode.prototype.hasOwnProperty('text')) {
|
|
Object.defineProperty(Parser.SyntaxNode.prototype, 'text', {
|
|
get () {
|
|
return this.tree.buffer.getTextInRange(new Range(this.startPosition, this.endPosition))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
constructor ({buffer, grammar, config, grammars}) {
|
|
TreeSitterLanguageMode._patchSyntaxNode()
|
|
this.id = nextId++
|
|
this.buffer = buffer
|
|
this.grammar = grammar
|
|
this.config = config
|
|
this.grammarRegistry = grammars
|
|
this.parser = new Parser()
|
|
this.rootLanguageLayer = new LanguageLayer(this, grammar)
|
|
this.injectionsMarkerLayer = buffer.addMarkerLayer()
|
|
|
|
this.rootScopeDescriptor = new ScopeDescriptor({scopes: [this.grammar.id]})
|
|
this.emitter = new Emitter()
|
|
this.isFoldableCache = []
|
|
this.hasQueuedParse = false
|
|
|
|
this.grammarForLanguageString = this.grammarForLanguageString.bind(this)
|
|
this.emitRangeUpdate = this.emitRangeUpdate.bind(this)
|
|
|
|
this.subscription = this.buffer.onDidChangeText(({changes}) => {
|
|
for (let i = changes.length - 1; i >= 0; i--) {
|
|
const {oldRange, newRange} = changes[i]
|
|
const startRow = oldRange.start.row
|
|
const oldEndRow = oldRange.end.row
|
|
const newEndRow = newRange.end.row
|
|
this.isFoldableCache.splice(
|
|
startRow,
|
|
oldEndRow - startRow,
|
|
...new Array(newEndRow - startRow)
|
|
)
|
|
}
|
|
|
|
this.rootLanguageLayer.update(null)
|
|
})
|
|
|
|
this.rootLanguageLayer.update(null)
|
|
|
|
// TODO: Remove this once TreeSitterLanguageMode implements its own auto-indentation system. This
|
|
// is temporarily needed in order to delegate to the TextMateLanguageMode's auto-indent system.
|
|
this.regexesByPattern = {}
|
|
}
|
|
|
|
destroy () {
|
|
this.injectionsMarkerLayer.destroy()
|
|
this.subscription.dispose()
|
|
this.rootLanguageLayer = null
|
|
this.parser = null
|
|
}
|
|
|
|
getLanguageId () {
|
|
return this.grammar.id
|
|
}
|
|
|
|
bufferDidChange (change) {
|
|
this.rootLanguageLayer.handleTextChange(change)
|
|
for (const marker of this.injectionsMarkerLayer.getMarkers()) {
|
|
marker.languageLayer.handleTextChange(change)
|
|
}
|
|
}
|
|
|
|
async parse (language, oldTree, ranges) {
|
|
const parser = PARSER_POOL.pop() || new Parser()
|
|
parser.setLanguage(language)
|
|
const newTree = await parser.parseTextBuffer(this.buffer.buffer, oldTree, {
|
|
syncOperationLimit: 1000,
|
|
includedRanges: ranges
|
|
})
|
|
PARSER_POOL.push(parser)
|
|
return newTree
|
|
}
|
|
|
|
get tree () {
|
|
return this.rootLanguageLayer.tree
|
|
}
|
|
|
|
updateForInjection (grammar) {
|
|
this.rootLanguageLayer.updateInjections(grammar)
|
|
}
|
|
|
|
/*
|
|
Section - Highlighting
|
|
*/
|
|
|
|
buildHighlightIterator () {
|
|
const layerIterators = [
|
|
this.rootLanguageLayer.buildHighlightIterator(),
|
|
...this.injectionsMarkerLayer.getMarkers().map(m => m.languageLayer.buildHighlightIterator())
|
|
]
|
|
return new HighlightIterator(this, layerIterators)
|
|
}
|
|
|
|
onDidChangeHighlighting (callback) {
|
|
return this.emitter.on('did-change-highlighting', callback)
|
|
}
|
|
|
|
classNameForScopeId (scopeId) {
|
|
return this.grammar.classNameForScopeId(scopeId)
|
|
}
|
|
|
|
/*
|
|
Section - Commenting
|
|
*/
|
|
|
|
commentStringsForPosition () {
|
|
return this.grammar.commentStrings
|
|
}
|
|
|
|
isRowCommented () {
|
|
return false
|
|
}
|
|
|
|
/*
|
|
Section - Indentation
|
|
*/
|
|
|
|
suggestedIndentForLineAtBufferRow (row, line, tabLength) {
|
|
return this._suggestedIndentForLineWithScopeAtBufferRow(
|
|
row,
|
|
line,
|
|
this.rootScopeDescriptor,
|
|
tabLength
|
|
)
|
|
}
|
|
|
|
suggestedIndentForBufferRow (row, tabLength, options) {
|
|
return this._suggestedIndentForLineWithScopeAtBufferRow(
|
|
row,
|
|
this.buffer.lineForRow(row),
|
|
this.rootScopeDescriptor,
|
|
tabLength,
|
|
options
|
|
)
|
|
}
|
|
|
|
indentLevelForLine (line, tabLength = 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
|
|
}
|
|
|
|
/*
|
|
Section - Folding
|
|
*/
|
|
|
|
isFoldableAtRow (row) {
|
|
if (this.isFoldableCache[row] != null) return this.isFoldableCache[row]
|
|
const result = this.getFoldableRangeContainingPoint(Point(row, Infinity), 0, true) != null
|
|
this.isFoldableCache[row] = result
|
|
return result
|
|
}
|
|
|
|
getFoldableRanges () {
|
|
return this.getFoldableRangesAtIndentLevel(null)
|
|
}
|
|
|
|
/**
|
|
* TODO: Make this method generate folds for nested languages (currently,
|
|
* folds are only generated for the root language layer).
|
|
*/
|
|
getFoldableRangesAtIndentLevel (goalLevel) {
|
|
let result = []
|
|
let stack = [{node: this.tree.rootNode, level: 0}]
|
|
while (stack.length > 0) {
|
|
const {node, level} = stack.pop()
|
|
|
|
const range = this.getFoldableRangeForNode(node, this.grammar)
|
|
if (range) {
|
|
if (goalLevel == null || level === goalLevel) {
|
|
let updatedExistingRange = false
|
|
for (let i = 0, {length} = result; i < length; i++) {
|
|
if (result[i].start.row === range.start.row &&
|
|
result[i].end.row === range.end.row) {
|
|
result[i] = range
|
|
updatedExistingRange = true
|
|
break
|
|
}
|
|
}
|
|
if (!updatedExistingRange) result.push(range)
|
|
}
|
|
}
|
|
|
|
const parentStartRow = node.startPosition.row
|
|
const parentEndRow = node.endPosition.row
|
|
for (let children = node.namedChildren, i = 0, {length} = children; i < length; i++) {
|
|
const child = children[i]
|
|
const {startPosition: childStart, endPosition: childEnd} = child
|
|
if (childEnd.row > childStart.row) {
|
|
if (childStart.row === parentStartRow && childEnd.row === parentEndRow) {
|
|
stack.push({node: child, level: level})
|
|
} else {
|
|
const childLevel = range && range.containsPoint(childStart) && range.containsPoint(childEnd)
|
|
? level + 1
|
|
: level
|
|
if (childLevel <= goalLevel || goalLevel == null) {
|
|
stack.push({node: child, level: childLevel})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result.sort((a, b) => a.start.row - b.start.row)
|
|
}
|
|
|
|
getFoldableRangeContainingPoint (point, tabLength, existenceOnly = false) {
|
|
if (!this.tree) return null
|
|
|
|
let smallestRange
|
|
this._forEachTreeWithRange(new Range(point, point), (tree, grammar) => {
|
|
let node = tree.rootNode.descendantForPosition(this.buffer.clipPosition(point))
|
|
while (node) {
|
|
if (existenceOnly && node.startPosition.row < point.row) return
|
|
if (node.endPosition.row > point.row) {
|
|
const range = this.getFoldableRangeForNode(node, grammar)
|
|
if (range && rangeIsSmaller(range, smallestRange)) {
|
|
smallestRange = range
|
|
return
|
|
}
|
|
}
|
|
node = node.parent
|
|
}
|
|
})
|
|
|
|
return existenceOnly
|
|
? smallestRange && smallestRange.start.row === point.row
|
|
: smallestRange
|
|
}
|
|
|
|
_forEachTreeWithRange (range, callback) {
|
|
callback(this.rootLanguageLayer.tree, this.rootLanguageLayer.grammar)
|
|
|
|
const injectionMarkers = this.injectionsMarkerLayer.findMarkers({
|
|
intersectsRange: range
|
|
})
|
|
|
|
for (const injectionMarker of injectionMarkers) {
|
|
const {tree, grammar} = injectionMarker.languageLayer
|
|
if (tree) callback(tree, grammar)
|
|
}
|
|
}
|
|
|
|
getFoldableRangeForNode (node, grammar, existenceOnly) {
|
|
const {children, type: nodeType} = node
|
|
const childCount = children.length
|
|
let childTypes
|
|
|
|
for (var i = 0, {length} = grammar.folds; i < length; i++) {
|
|
const foldEntry = grammar.folds[i]
|
|
|
|
if (foldEntry.type) {
|
|
if (typeof foldEntry.type === 'string') {
|
|
if (foldEntry.type !== nodeType) continue
|
|
} else {
|
|
if (!foldEntry.type.includes(nodeType)) continue
|
|
}
|
|
}
|
|
|
|
let foldStart
|
|
const startEntry = foldEntry.start
|
|
if (startEntry) {
|
|
if (startEntry.index != null) {
|
|
const child = children[startEntry.index]
|
|
if (!child || (startEntry.type && startEntry.type !== child.type)) continue
|
|
foldStart = child.endPosition
|
|
} else {
|
|
if (!childTypes) childTypes = children.map(child => child.type)
|
|
const index = typeof startEntry.type === 'string'
|
|
? childTypes.indexOf(startEntry.type)
|
|
: childTypes.findIndex(type => startEntry.type.includes(type))
|
|
if (index === -1) continue
|
|
foldStart = children[index].endPosition
|
|
}
|
|
} else {
|
|
foldStart = new Point(node.startPosition.row, Infinity)
|
|
}
|
|
|
|
let foldEnd
|
|
const endEntry = foldEntry.end
|
|
if (endEntry) {
|
|
let foldEndNode
|
|
if (endEntry.index != null) {
|
|
const index = endEntry.index < 0 ? childCount + endEntry.index : endEntry.index
|
|
foldEndNode = children[index]
|
|
if (!foldEndNode || (endEntry.type && endEntry.type !== foldEndNode.type)) continue
|
|
} else {
|
|
if (!childTypes) childTypes = children.map(foldEndNode => foldEndNode.type)
|
|
const index = typeof endEntry.type === 'string'
|
|
? childTypes.indexOf(endEntry.type)
|
|
: childTypes.findIndex(type => endEntry.type.includes(type))
|
|
if (index === -1) continue
|
|
foldEndNode = children[index]
|
|
}
|
|
|
|
if (foldEndNode.endIndex - foldEndNode.startIndex > 1 && foldEndNode.startPosition.row > foldStart.row) {
|
|
foldEnd = new Point(foldEndNode.startPosition.row - 1, Infinity)
|
|
} else {
|
|
foldEnd = foldEndNode.startPosition
|
|
if (!pointIsGreater(foldEnd, foldStart)) continue
|
|
}
|
|
} else {
|
|
const {endPosition} = node
|
|
if (endPosition.column === 0) {
|
|
foldEnd = Point(endPosition.row - 1, Infinity)
|
|
} else if (childCount > 0) {
|
|
foldEnd = endPosition
|
|
} else {
|
|
foldEnd = Point(endPosition.row, 0)
|
|
}
|
|
}
|
|
|
|
return existenceOnly ? true : new Range(foldStart, foldEnd)
|
|
}
|
|
}
|
|
|
|
/*
|
|
Section - Syntax Tree APIs
|
|
*/
|
|
|
|
getRangeForSyntaxNodeContainingRange (range) {
|
|
const startIndex = this.buffer.characterIndexForPosition(range.start)
|
|
const endIndex = this.buffer.characterIndexForPosition(range.end)
|
|
const searchEndIndex = Math.max(0, endIndex - 1)
|
|
|
|
let smallestNode
|
|
this._forEachTreeWithRange(range, tree => {
|
|
let node = tree.rootNode.descendantForIndex(startIndex, searchEndIndex)
|
|
while (node && !nodeContainsIndices(node, startIndex, endIndex)) {
|
|
node = node.parent
|
|
}
|
|
if (nodeIsSmaller(node, smallestNode)) smallestNode = node
|
|
})
|
|
|
|
if (smallestNode) return rangeForNode(smallestNode)
|
|
}
|
|
|
|
bufferRangeForScopeAtPosition (position) {
|
|
return this.getRangeForSyntaxNodeContainingRange(new Range(position, position))
|
|
}
|
|
|
|
/*
|
|
Section - Backward compatibility shims
|
|
*/
|
|
|
|
onDidTokenize (callback) { return new Disposable(() => {}) }
|
|
|
|
tokenizedLineForRow (row) {
|
|
return new TokenizedLine({
|
|
openScopes: [],
|
|
text: this.buffer.lineForRow(row),
|
|
tags: [],
|
|
ruleStack: [],
|
|
lineEnding: this.buffer.lineEndingForRow(row),
|
|
tokenIterator: null,
|
|
grammar: this.grammar
|
|
})
|
|
}
|
|
|
|
scopeDescriptorForPosition (point) {
|
|
if (!this.tree) return this.rootScopeDescriptor
|
|
|
|
point = Point.fromObject(point)
|
|
let node = this.tree.rootNode.descendantForPosition(point)
|
|
|
|
// Don't include anonymous token types like '(' because they prevent scope chains
|
|
// from being parsed as CSS selectors by the `slick` parser. Other css selector
|
|
// parsers like `postcss-selector-parser` do allow arbitrary quoted strings in
|
|
// selectors.
|
|
if (!node.isNamed) node = node.parent
|
|
|
|
const result = []
|
|
while (node) {
|
|
result.push(node.type)
|
|
node = node.parent
|
|
}
|
|
result.push(this.grammar.id)
|
|
return new ScopeDescriptor({scopes: result.reverse()})
|
|
}
|
|
|
|
getGrammar () {
|
|
return this.grammar
|
|
}
|
|
|
|
/*
|
|
Section - Private
|
|
*/
|
|
|
|
grammarForLanguageString (languageString) {
|
|
return this.grammarRegistry.treeSitterGrammarForLanguageString(languageString)
|
|
}
|
|
|
|
emitRangeUpdate (range) {
|
|
const startRow = range.start.row
|
|
const endRow = range.end.row
|
|
for (let row = startRow; row < endRow; row++) {
|
|
this.isFoldableCache[row] = undefined
|
|
}
|
|
this.emitter.emit('did-change-highlighting', range)
|
|
}
|
|
}
|
|
|
|
class LanguageLayer {
|
|
constructor (languageMode, grammar, contentChildTypes) {
|
|
this.languageMode = languageMode
|
|
this.grammar = grammar
|
|
this.tree = null
|
|
this.currentParsePromise = null
|
|
this.patchSinceCurrentParseStarted = null
|
|
this.contentChildTypes = contentChildTypes
|
|
}
|
|
|
|
buildHighlightIterator () {
|
|
if (this.tree) {
|
|
return new LayerHighlightIterator(this, this.tree.walk())
|
|
} else {
|
|
return new NullHighlightIterator()
|
|
}
|
|
}
|
|
|
|
handleTextChange ({oldRange, newRange, oldText, newText}) {
|
|
if (this.tree) {
|
|
this.tree.edit(this._treeEditForBufferChange(
|
|
oldRange.start, oldRange.end, newRange.end, oldText, newText
|
|
))
|
|
|
|
if (this.editedRange) {
|
|
if (newRange.start.isLessThan(this.editedRange.start)) {
|
|
this.editedRange.start = newRange.start
|
|
}
|
|
if (oldRange.end.isLessThan(this.editedRange.end)) {
|
|
this.editedRange.end = newRange.end.traverse(this.editedRange.end.traversalFrom(oldRange.end))
|
|
} else {
|
|
this.editedRange.end = newRange.end
|
|
}
|
|
} else {
|
|
this.editedRange = newRange.copy()
|
|
}
|
|
}
|
|
|
|
if (this.patchSinceCurrentParseStarted) {
|
|
this.patchSinceCurrentParseStarted.splice(
|
|
oldRange.start,
|
|
oldRange.end,
|
|
newRange.end,
|
|
oldText,
|
|
newText
|
|
)
|
|
}
|
|
}
|
|
|
|
destroy () {
|
|
for (const marker of this.languageMode.injectionsMarkerLayer.getMarkers()) {
|
|
if (marker.parentLanguageLayer === this) {
|
|
marker.languageLayer.destroy()
|
|
marker.destroy()
|
|
}
|
|
}
|
|
}
|
|
|
|
async update (nodeRangeSet) {
|
|
if (!this.currentParsePromise) {
|
|
do {
|
|
this.currentParsePromise = this._performUpdate(nodeRangeSet)
|
|
await this.currentParsePromise
|
|
} while (this.tree && this.tree.rootNode.hasChanges())
|
|
this.currentParsePromise = null
|
|
}
|
|
}
|
|
|
|
updateInjections (grammar) {
|
|
if (grammar.injectionRegExp) {
|
|
if (!this.currentParsePromise) this.currentParsePromise = Promise.resolve()
|
|
this.currentParsePromise = this.currentParsePromise.then(async () => {
|
|
await this._populateInjections(MAX_RANGE, null)
|
|
this.currentParsePromise = null
|
|
})
|
|
}
|
|
}
|
|
|
|
async _performUpdate (nodeRangeSet) {
|
|
let includedRanges = null
|
|
if (nodeRangeSet) {
|
|
includedRanges = nodeRangeSet.getRanges()
|
|
if (includedRanges.length === 0) {
|
|
this.tree = null
|
|
return
|
|
}
|
|
}
|
|
|
|
let affectedRange = this.editedRange
|
|
this.editedRange = null
|
|
|
|
this.patchSinceCurrentParseStarted = new Patch()
|
|
const tree = await this.languageMode.parse(
|
|
this.grammar.languageModule,
|
|
this.tree,
|
|
includedRanges
|
|
)
|
|
tree.buffer = this.languageMode.buffer
|
|
|
|
const changes = this.patchSinceCurrentParseStarted.getChanges()
|
|
this.patchSinceCurrentParseStarted = null
|
|
for (let i = changes.length - 1; i >= 0; i--) {
|
|
const {oldStart, oldEnd, newEnd, oldText, newText} = changes[i]
|
|
tree.edit(this._treeEditForBufferChange(
|
|
oldStart, oldEnd, newEnd, oldText, newText
|
|
))
|
|
}
|
|
|
|
if (this.tree) {
|
|
const rangesWithSyntaxChanges = this.tree.getChangedRanges(tree)
|
|
this.tree = tree
|
|
|
|
if (!affectedRange) return
|
|
if (rangesWithSyntaxChanges.length > 0) {
|
|
for (const range of rangesWithSyntaxChanges) {
|
|
this.languageMode.emitRangeUpdate(rangeForNode(range))
|
|
}
|
|
|
|
affectedRange = affectedRange.union(new Range(
|
|
rangesWithSyntaxChanges[0].startPosition,
|
|
last(rangesWithSyntaxChanges).endPosition
|
|
))
|
|
} else {
|
|
this.languageMode.emitRangeUpdate(affectedRange)
|
|
}
|
|
} else {
|
|
this.tree = tree
|
|
this.languageMode.emitRangeUpdate(rangeForNode(tree.rootNode))
|
|
if (includedRanges) {
|
|
affectedRange = new Range(includedRanges[0].startPosition, last(includedRanges).endPosition)
|
|
} else {
|
|
affectedRange = MAX_RANGE
|
|
}
|
|
}
|
|
|
|
await this._populateInjections(affectedRange, nodeRangeSet)
|
|
}
|
|
|
|
_populateInjections (range, nodeRangeSet) {
|
|
const {injectionsMarkerLayer, grammarForLanguageString} = this.languageMode
|
|
|
|
const existingInjectionMarkers = injectionsMarkerLayer
|
|
.findMarkers({intersectsRange: range})
|
|
.filter(marker => marker.parentLanguageLayer === this)
|
|
|
|
if (existingInjectionMarkers.length > 0) {
|
|
range = range.union(new Range(
|
|
existingInjectionMarkers[0].getRange().start,
|
|
last(existingInjectionMarkers).getRange().end
|
|
))
|
|
}
|
|
|
|
const markersToUpdate = new Map()
|
|
for (const injectionPoint of this.grammar.injectionPoints) {
|
|
const nodes = this.tree.rootNode.descendantsOfType(
|
|
injectionPoint.type,
|
|
range.start,
|
|
range.end
|
|
)
|
|
|
|
for (const node of nodes) {
|
|
const languageName = injectionPoint.language(node)
|
|
if (!languageName) continue
|
|
|
|
const grammar = grammarForLanguageString(languageName)
|
|
if (!grammar) continue
|
|
|
|
const contentNodes = injectionPoint.content(node)
|
|
if (!contentNodes) continue
|
|
|
|
const injectionNodes = [].concat(contentNodes)
|
|
if (!injectionNodes.length) continue
|
|
|
|
const injectionRange = rangeForNode(node)
|
|
let marker = existingInjectionMarkers.find(m =>
|
|
m.getRange().isEqual(injectionRange) &&
|
|
m.languageLayer.grammar === grammar
|
|
)
|
|
if (!marker) {
|
|
marker = injectionsMarkerLayer.markRange(injectionRange)
|
|
marker.languageLayer = new LanguageLayer(this.languageMode, grammar, injectionPoint.contentChildTypes)
|
|
marker.parentLanguageLayer = this
|
|
}
|
|
|
|
markersToUpdate.set(marker, new NodeRangeSet(nodeRangeSet, injectionNodes))
|
|
}
|
|
}
|
|
|
|
for (const marker of existingInjectionMarkers) {
|
|
if (!markersToUpdate.has(marker)) {
|
|
marker.languageLayer.destroy()
|
|
this.languageMode.emitRangeUpdate(marker.getRange())
|
|
marker.destroy()
|
|
}
|
|
}
|
|
|
|
const promises = []
|
|
for (const [marker, nodeRangeSet] of markersToUpdate) {
|
|
promises.push(marker.languageLayer.update(nodeRangeSet))
|
|
}
|
|
return Promise.all(promises)
|
|
}
|
|
|
|
_treeEditForBufferChange (start, oldEnd, newEnd, oldText, newText) {
|
|
const startIndex = this.languageMode.buffer.characterIndexForPosition(start)
|
|
return {
|
|
startIndex,
|
|
oldEndIndex: startIndex + oldText.length,
|
|
newEndIndex: startIndex + newText.length,
|
|
startPosition: start,
|
|
oldEndPosition: oldEnd,
|
|
newEndPosition: newEnd
|
|
}
|
|
}
|
|
}
|
|
|
|
class HighlightIterator {
|
|
constructor (languageMode, iterators) {
|
|
this.languageMode = languageMode
|
|
this.iterators = iterators.sort((a, b) => b.getIndex() - a.getIndex())
|
|
}
|
|
|
|
seek (targetPosition) {
|
|
const containingTags = [], containingTagStartIndices = []
|
|
const targetIndex = this.languageMode.buffer.characterIndexForPosition(targetPosition)
|
|
for (let i = this.iterators.length - 1; i >= 0; i--) {
|
|
this.iterators[i].seek(targetIndex, containingTags, containingTagStartIndices)
|
|
}
|
|
this.iterators.sort((a, b) => b.getIndex() - a.getIndex())
|
|
return containingTags
|
|
}
|
|
|
|
moveToSuccessor () {
|
|
const lastIndex = this.iterators.length - 1
|
|
const leader = this.iterators[lastIndex]
|
|
leader.moveToSuccessor()
|
|
const leaderCharIndex = leader.getIndex()
|
|
let i = lastIndex
|
|
while (i > 0 && this.iterators[i - 1].getIndex() < leaderCharIndex) i--
|
|
if (i < lastIndex) this.iterators.splice(i, 0, this.iterators.pop())
|
|
}
|
|
|
|
getPosition () {
|
|
return last(this.iterators).getPosition()
|
|
}
|
|
|
|
getCloseScopeIds () {
|
|
return last(this.iterators).getCloseScopeIds()
|
|
}
|
|
|
|
getOpenScopeIds () {
|
|
return last(this.iterators).getOpenScopeIds()
|
|
}
|
|
}
|
|
|
|
class LayerHighlightIterator {
|
|
constructor (languageLayer, treeCursor) {
|
|
this.languageLayer = languageLayer
|
|
this.treeCursor = treeCursor
|
|
this.atEnd = false
|
|
|
|
// In order to determine which selectors match its current node, the iterator maintains
|
|
// a list of the current node's ancestors. Because the selectors can use the `:nth-child`
|
|
// pseudo-class, each node's child index is also stored.
|
|
this.containingNodeTypes = []
|
|
this.containingNodeChildIndices = []
|
|
this.containingNodeEndIndices = []
|
|
|
|
// At any given position, the iterator exposes the list of class names that should be
|
|
// *ended* at its current position and the list of class names that should be *started*
|
|
// at its current position.
|
|
this.closeTags = []
|
|
this.openTags = []
|
|
}
|
|
|
|
seek (targetIndex, containingTags, containingTagStartIndices) {
|
|
while (this.treeCursor.gotoParent()) {}
|
|
|
|
this.done = false
|
|
this.atEnd = true
|
|
this.closeTags.length = 0
|
|
this.openTags.length = 0
|
|
this.containingNodeTypes.length = 0
|
|
this.containingNodeChildIndices.length = 0
|
|
this.containingNodeEndIndices.length = 0
|
|
|
|
if (targetIndex >= this.treeCursor.endIndex) {
|
|
this.done = true
|
|
return
|
|
}
|
|
|
|
let childIndex = -1
|
|
for (;;) {
|
|
this.containingNodeTypes.push(this.treeCursor.nodeType)
|
|
this.containingNodeChildIndices.push(childIndex)
|
|
this.containingNodeEndIndices.push(this.treeCursor.endIndex)
|
|
|
|
const scopeName = this.currentScopeName()
|
|
if (scopeName) {
|
|
const id = this.idForScope(scopeName)
|
|
if (this.treeCursor.startIndex < targetIndex) {
|
|
insertContainingTag(id, this.treeCursor.startIndex, containingTags, containingTagStartIndices)
|
|
} else {
|
|
this.atEnd = false
|
|
this.openTags.push(id)
|
|
while (this.treeCursor.gotoFirstChild()) {
|
|
this.containingNodeTypes.push(this.treeCursor.nodeType)
|
|
this.containingNodeChildIndices.push(0)
|
|
const scopeName = this.currentScopeName()
|
|
if (scopeName) {
|
|
this.openTags.push(this.idForScope(scopeName))
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
childIndex = this.treeCursor.gotoFirstChildForIndex(targetIndex)
|
|
if (childIndex === null) break
|
|
if (this.treeCursor.startIndex >= targetIndex) this.atEnd = false
|
|
}
|
|
|
|
return containingTags
|
|
}
|
|
|
|
moveToSuccessor () {
|
|
let didMove = false
|
|
this.closeTags.length = 0
|
|
this.openTags.length = 0
|
|
|
|
if (this.done) return
|
|
|
|
while (true) {
|
|
if (this.atEnd) {
|
|
if (this.treeCursor.gotoNextSibling()) {
|
|
didMove = true
|
|
this.atEnd = false
|
|
const depth = this.containingNodeTypes.length
|
|
this.containingNodeTypes[depth - 1] = this.treeCursor.nodeType
|
|
this.containingNodeChildIndices[depth - 1]++
|
|
this.containingNodeEndIndices[depth - 1] = this.treeCursor.endIndex
|
|
|
|
while (true) {
|
|
const {startIndex} = this.treeCursor
|
|
const scopeName = this.currentScopeName()
|
|
if (scopeName) {
|
|
this.openTags.push(this.idForScope(scopeName))
|
|
}
|
|
|
|
if (this.treeCursor.gotoFirstChild()) {
|
|
if ((this.closeTags.length || this.openTags.length) &&
|
|
this.treeCursor.startIndex > startIndex) {
|
|
this.treeCursor.gotoParent()
|
|
break
|
|
}
|
|
|
|
this.containingNodeTypes.push(this.treeCursor.nodeType)
|
|
this.containingNodeChildIndices.push(0)
|
|
this.containingNodeEndIndices.push(this.treeCursor.endIndex)
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
} else if (this.treeCursor.gotoParent()) {
|
|
this.atEnd = false
|
|
this.containingNodeTypes.pop()
|
|
this.containingNodeChildIndices.pop()
|
|
this.containingNodeEndIndices.pop()
|
|
} else {
|
|
this.done = true
|
|
break
|
|
}
|
|
} else {
|
|
this.atEnd = true
|
|
didMove = true
|
|
|
|
const scopeName = this.currentScopeName()
|
|
if (scopeName) {
|
|
this.closeTags.push(this.idForScope(scopeName))
|
|
}
|
|
|
|
const endIndex = this.treeCursor.endIndex
|
|
let depth = this.containingNodeEndIndices.length
|
|
while (depth > 1 && this.containingNodeEndIndices[depth - 2] === endIndex) {
|
|
this.treeCursor.gotoParent()
|
|
this.containingNodeTypes.pop()
|
|
this.containingNodeChildIndices.pop()
|
|
this.containingNodeEndIndices.pop()
|
|
--depth
|
|
const scopeName = this.currentScopeName()
|
|
if (scopeName) this.closeTags.push(this.idForScope(scopeName))
|
|
}
|
|
}
|
|
|
|
if (didMove && (this.closeTags.length || this.openTags.length)) break
|
|
}
|
|
}
|
|
|
|
getPosition () {
|
|
if (this.done) {
|
|
return Point.INFINITY
|
|
} else if (this.atEnd) {
|
|
return this.treeCursor.endPosition
|
|
} else {
|
|
return this.treeCursor.startPosition
|
|
}
|
|
}
|
|
|
|
getIndex () {
|
|
if (this.done) {
|
|
return Infinity
|
|
} else if (this.atEnd) {
|
|
return this.treeCursor.endIndex
|
|
} else {
|
|
return this.treeCursor.startIndex
|
|
}
|
|
}
|
|
|
|
getCloseScopeIds () {
|
|
return this.closeTags.slice()
|
|
}
|
|
|
|
getOpenScopeIds () {
|
|
return this.openTags.slice()
|
|
}
|
|
|
|
// Private methods
|
|
|
|
currentScopeName () {
|
|
return this.languageLayer.grammar.scopeMap.get(
|
|
this.containingNodeTypes,
|
|
this.containingNodeChildIndices,
|
|
this.treeCursor.nodeIsNamed
|
|
)
|
|
}
|
|
|
|
idForScope (scopeName) {
|
|
return this.languageLayer.languageMode.grammar.idForScope(scopeName)
|
|
}
|
|
}
|
|
|
|
class NullHighlightIterator {
|
|
seek () { return [] }
|
|
moveToSuccessor () {}
|
|
getIndex () { return Infinity }
|
|
getPosition () { return Point.INFINITY }
|
|
getOpenScopeIds () { return [] }
|
|
getCloseScopeIds () { return [] }
|
|
}
|
|
|
|
class NodeRangeSet {
|
|
constructor (previous, nodes) {
|
|
this.previous = previous
|
|
this.nodes = nodes
|
|
}
|
|
|
|
getRanges () {
|
|
const previousRanges = this.previous && this.previous.getRanges()
|
|
const result = []
|
|
|
|
for (const node of this.nodes) {
|
|
let position = node.startPosition
|
|
let index = node.startIndex
|
|
|
|
for (const child of node.children) {
|
|
const nextPosition = child.startPosition
|
|
const nextIndex = child.startIndex
|
|
if (nextIndex > index) {
|
|
this._pushRange(previousRanges, result, {
|
|
startIndex: index,
|
|
endIndex: nextIndex,
|
|
startPosition: position,
|
|
endPosition: nextPosition
|
|
})
|
|
}
|
|
position = child.endPosition
|
|
index = child.endIndex
|
|
}
|
|
|
|
if (node.endIndex > index) {
|
|
this._pushRange(previousRanges, result, {
|
|
startIndex: index,
|
|
endIndex: node.endIndex,
|
|
startPosition: position,
|
|
endPosition: node.endPosition
|
|
})
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
_pushRange (previousRanges, newRanges, newRange) {
|
|
if (!previousRanges) {
|
|
newRanges.push(newRange)
|
|
return
|
|
}
|
|
|
|
for (const previousRange of previousRanges) {
|
|
if (previousRange.endIndex <= newRange.startIndex) continue
|
|
if (previousRange.startIndex >= newRange.endIndex) break
|
|
newRanges.push({
|
|
startIndex: Math.max(previousRange.startIndex, newRange.startIndex),
|
|
endIndex: Math.min(previousRange.endIndex, newRange.endIndex),
|
|
startPosition: Point.max(previousRange.startPosition, newRange.startPosition),
|
|
endPosition: Point.min(previousRange.endPosition, newRange.endPosition)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
function insertContainingTag (tag, index, tags, indices) {
|
|
const i = indices.findIndex(existingIndex => existingIndex > index)
|
|
if (i === -1) {
|
|
tags.push(tag)
|
|
indices.push(index)
|
|
} else {
|
|
tags.splice(i, 0, tag)
|
|
indices.splice(i, 0, index)
|
|
}
|
|
}
|
|
|
|
// Return true iff `mouse` is smaller than `house`. Only correct if
|
|
// mouse and house overlap.
|
|
//
|
|
// * `mouse` {Range}
|
|
// * `house` {Range}
|
|
function rangeIsSmaller (mouse, house) {
|
|
if (!house) return true
|
|
const mvec = vecFromRange(mouse)
|
|
const hvec = vecFromRange(house)
|
|
return Point.min(mvec, hvec) === mvec
|
|
}
|
|
|
|
function vecFromRange ({start, end}) {
|
|
return end.translate(start.negate())
|
|
}
|
|
|
|
function rangeForNode (node) {
|
|
return new Range(node.startPosition, node.endPosition)
|
|
}
|
|
|
|
function nodeContainsIndices (node, start, end) {
|
|
if (node.startIndex < start) return node.endIndex >= end
|
|
if (node.startIndex === start) return node.endIndex > end
|
|
return false
|
|
}
|
|
|
|
function nodeIsSmaller (left, right) {
|
|
if (!left) return false
|
|
if (!right) return true
|
|
return left.endIndex - left.startIndex < right.endIndex - right.startIndex
|
|
}
|
|
|
|
function pointIsGreater (left, right) {
|
|
return left.row > right.row || left.row === right.row && left.column > right.column
|
|
}
|
|
|
|
function last (array) {
|
|
return array[array.length - 1]
|
|
}
|
|
|
|
// TODO: Remove this once TreeSitterLanguageMode implements its own auto-indent system.
|
|
[
|
|
'_suggestedIndentForLineWithScopeAtBufferRow',
|
|
'suggestedIndentForEditedBufferRow',
|
|
'increaseIndentRegexForScopeDescriptor',
|
|
'decreaseIndentRegexForScopeDescriptor',
|
|
'decreaseNextIndentRegexForScopeDescriptor',
|
|
'regexForPattern'
|
|
].forEach(methodName => {
|
|
TreeSitterLanguageMode.prototype[methodName] = TextMateLanguageMode.prototype[methodName]
|
|
})
|
|
|
|
TreeSitterLanguageMode.LanguageLayer = LanguageLayer
|
|
|
|
module.exports = TreeSitterLanguageMode
|