Make tree-sitter scope descriptors match HTML classes, not syntax tree

This commit is contained in:
Max Brunsfeld
2018-08-20 10:01:13 -07:00
parent 760b38c54b
commit fd6f9d39bd
3 changed files with 50 additions and 70 deletions

View File

@@ -1323,39 +1323,49 @@ describe('TreeSitterLanguageMode', () => {
describe('.scopeDescriptorForPosition', () => {
it('returns a scope descriptor representing the given position in the syntax tree', async () => {
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
scopeName: 'javascript',
parser: 'tree-sitter-javascript'
scopeName: 'source.js',
parser: 'tree-sitter-javascript',
scopes: {
program: 'source.js',
property_identifier: 'property.name'
}
})
buffer.setText('foo({bar: baz});')
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
await nextHighlightingUpdate(buffer.getLanguageMode())
expect(editor.scopeDescriptorForBufferPosition([0, 6]).getScopesArray()).toEqual([
'javascript',
'program',
'expression_statement',
'call_expression',
'arguments',
'object',
'pair',
'property_identifier'
expect(editor.scopeDescriptorForBufferPosition([0, 'foo({b'.length]).getScopesArray()).toEqual([
'source.js',
'property.name'
])
expect(editor.scopeDescriptorForBufferPosition([0, 'foo({'.length]).getScopesArray()).toEqual([
'source.js',
'property.name'
])
})
it('includes nodes in injected syntax trees', async () => {
const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
scopeName: 'javascript',
scopeName: 'source.js',
parser: 'tree-sitter-javascript',
scopes: {},
scopes: {
program: 'source.js',
template_string: 'string.quoted',
interpolation: 'meta.embedded',
property_identifier: 'property.name'
},
injectionRegExp: 'javascript',
injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT]
})
const htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
scopeName: 'html',
scopeName: 'text.html',
parser: 'tree-sitter-html',
scopes: {},
scopes: {
fragment: 'text.html',
raw_element: 'script.tag'
},
injectionRegExp: 'html',
injectionPoints: [SCRIPT_TAG_INJECTION_POINT]
})
@@ -1381,20 +1391,12 @@ describe('TreeSitterLanguageMode', () => {
const position = buffer.findSync('name').start
expect(languageMode.scopeDescriptorForPosition(position).getScopesArray()).toEqual([
'html',
'fragment',
'element',
'raw_element',
'raw_text',
'program',
'expression_statement',
'call_expression',
'template_string',
'fragment',
'element',
'template_substitution',
'member_expression',
'property_identifier'
'text.html',
'script.tag',
'source.js',
'string.quoted',
'text.html',
'property.name'
])
})
})

View File

@@ -52,6 +52,7 @@ class TreeSitterGrammar {
this.languageModule = require(languageModulePath)
this.scopesById = new Map()
this.conciseScopesById = new Map()
this.idsByScope = {}
this.nextScopeId = 256 + 1
this.registration = null
@@ -75,6 +76,17 @@ class TreeSitterGrammar {
return this.scopesById.get(id)
}
scopeNameForScopeId (id) {
let result = this.conciseScopesById.get(id)
if (!result) {
result = this.scopesById.get(id)
.slice('syntax--'.length)
.replace(/ syntax--/g, '.')
this.conciseScopesById.set(id, result)
}
return result
}
activate () {
this.registration = this.registry.addGrammar(this)
}

View File

@@ -398,42 +398,15 @@ class TreeSitterLanguageMode {
}
scopeDescriptorForPosition (point) {
if (!this.tree) return this.rootScopeDescriptor
point = Point.fromObject(point)
const iterators = []
this._forEachTreeWithRange(new Range(point, point), tree => {
const rootStartIndex = tree.rootNode.startIndex
let node = 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
iterators.push({node, rootStartIndex})
})
iterators.sort(compareScopeDescriptorIterators)
const iterator = this.buildHighlightIterator()
const scopes = []
for (;;) {
const {length} = iterators
if (!length) break
const iterator = iterators[length - 1]
scopes.push(iterator.node.type)
iterator.node = iterator.node.parent
if (iterator.node) {
let i = length - 1
while (i > 0 && compareScopeDescriptorIterators(iterator, iterators[i - 1]) < 0) i--
if (i < length - 1) iterators.splice(i, 0, iterators.pop())
} else {
iterators.pop()
}
for (const scope of iterator.seek(point)) {
scopes.push(this.grammar.scopeNameForScopeId(scope, false))
}
scopes.push(this.grammar.scopeName)
return new ScopeDescriptor({scopes: scopes.reverse()})
for (const scope of iterator.getOpenScopeIds()) {
scopes.push(this.grammar.scopeNameForScopeId(scope, false))
}
return new ScopeDescriptor({scopes})
}
getGrammar () {
@@ -1073,13 +1046,6 @@ function nodeIsSmaller (left, right) {
return left.endIndex - left.startIndex < right.endIndex - right.startIndex
}
function compareScopeDescriptorIterators (a, b) {
return (
a.node.startIndex - b.node.startIndex ||
a.rootStartIndex - b.rootStartIndex
)
}
function last (array) {
return array[array.length - 1]
}