Accept a TextMate scope selector in bufferRangeForScopeAtPosition

This commit is contained in:
Max Brunsfeld
2018-09-10 20:35:24 -07:00
parent d1292fd7e3
commit 0b6876c4c6
4 changed files with 72 additions and 65 deletions

View File

@@ -1504,20 +1504,21 @@ describe('TreeSitterLanguageMode', () => {
it('returns the range of the smallest matching node at position', async () => {
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
scopeName: 'javascript',
parser: 'tree-sitter-javascript'
parser: 'tree-sitter-javascript',
scopes: {
'property_identifier': 'variable.other.object.property',
'template_string': 'string.quoted.template'
}
})
buffer.setText('foo({bar: baz});')
buffer.setText('a(`${b({ccc: ddd})} eee`);')
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
expect(editor.bufferRangeForScopeAtPosition('.property_identifier', [0, 6])).toEqual(
buffer.findSync('bar')
expect(editor.bufferRangeForScopeAtPosition('.variable.property', [0, 9])).toEqual(
[[0, 8], [0, 11]]
)
expect(editor.bufferRangeForScopeAtPosition('.call_expression', [0, 6])).toEqual(
[[0, 0], [0, buffer.getText().length - 1]]
)
expect(editor.bufferRangeForScopeAtPosition('.object', [0, 9])).toEqual(
buffer.findSync('{bar: baz}')
expect(editor.bufferRangeForScopeAtPosition('.string.quoted', [0, 6])).toEqual(
[[0, 2], [0, 24]]
)
})
@@ -1525,7 +1526,9 @@ describe('TreeSitterLanguageMode', () => {
const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
scopeName: 'javascript',
parser: 'tree-sitter-javascript',
scopes: {},
scopes: {
'property_identifier': 'variable.other.object.property',
},
injectionRegExp: 'javascript',
injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT]
})
@@ -1533,7 +1536,9 @@ describe('TreeSitterLanguageMode', () => {
const htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
scopeName: 'html',
parser: 'tree-sitter-html',
scopes: {},
scopes: {
'element': 'meta.element.html'
},
injectionRegExp: 'html',
injectionPoints: [SCRIPT_TAG_INJECTION_POINT]
})
@@ -1557,9 +1562,9 @@ describe('TreeSitterLanguageMode', () => {
const nameProperty = buffer.findSync('name')
const {start} = nameProperty
const position = Object.assign({}, start, {column: start.column + 2})
expect(languageMode.bufferRangeForScopeAtPosition('.property_identifier', position))
expect(languageMode.bufferRangeForScopeAtPosition('.object.property', position))
.toEqual(nameProperty)
expect(languageMode.bufferRangeForScopeAtPosition('.element', position))
expect(languageMode.bufferRangeForScopeAtPosition('.meta.element.html', position))
.toEqual(buffer.findSync('<span>\\${person\\.name}</span>'))
})

View File

@@ -2,17 +2,17 @@ const parser = require('postcss-selector-parser')
module.exports =
class SyntaxScopeMap {
constructor (scopeNamesBySelector) {
constructor (resultsBySelector) {
this.namedScopeTable = {}
this.anonymousScopeTable = {}
for (let selector in scopeNamesBySelector) {
this.addSelector(selector, scopeNamesBySelector[selector])
for (let selector in resultsBySelector) {
this.addSelector(selector, resultsBySelector[selector])
}
setTableDefaults(this.namedScopeTable)
setTableDefaults(this.anonymousScopeTable)
}
addSelector (selector, scopeName) {
addSelector (selector, result) {
parser((parseResult) => {
for (let selectorNode of parseResult.nodes) {
let currentTable = null
@@ -91,7 +91,7 @@ class SyntaxScopeMap {
}
}
currentTable.scopeName = scopeName
currentTable.result = result
}
}).process(selector)
}
@@ -110,8 +110,8 @@ class SyntaxScopeMap {
currentTable = currentTable.indices[childIndices[i]]
}
if (currentTable.scopeName) {
result = currentTable.scopeName
if (currentTable.result != null) {
result = currentTable.result
}
if (i === 0) break
@@ -168,8 +168,8 @@ function mergeTable (table, defaultTable, mergeIndices = true) {
}
}
if (defaultTable.scopeName && !table.scopeName) {
table.scopeName = defaultTable.scopeName
if (defaultTable.result != null && table.result == null) {
table.result = defaultTable.result
}
}

View File

@@ -25,7 +25,7 @@ class TreeSitterGrammar {
const scopeSelectors = {}
for (const key in params.scopes || {}) {
const classes = toSyntaxClasses(params.scopes[key])
const classes = preprocessScopes(params.scopes[key])
const selectors = key.split(/,\s+/)
for (let selector of selectors) {
selector = selector.trim()
@@ -51,9 +51,9 @@ class TreeSitterGrammar {
})
this.languageModule = require(languageModulePath)
this.scopesById = new Map()
this.conciseScopesById = new Map()
this.idsByScope = {}
this.classNamesById = new Map()
this.scopeNamesById = new Map()
this.idsByScope = Object.create(null)
this.nextScopeId = 256 + 1
this.registration = null
}
@@ -62,29 +62,24 @@ class TreeSitterGrammar {
return `TreeSitterGrammar {scopeName: ${this.scopeName}}`
}
idForScope (scope) {
let id = this.idsByScope[scope]
idForScope (scopeName) {
let id = this.idsByScope[scopeName]
if (!id) {
id = this.nextScopeId += 2
this.idsByScope[scope] = id
this.scopesById.set(id, scope)
const className = scopeName.split('.').map(s => `syntax--${s}`).join(' ')
this.idsByScope[scopeName] = id
this.classNamesById.set(id, className)
this.scopeNamesById.set(id, scopeName)
}
return id
}
classNameForScopeId (id) {
return this.scopesById.get(id)
return this.classNamesById.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
return this.scopeNamesById.get(id)
}
activate () {
@@ -96,17 +91,14 @@ class TreeSitterGrammar {
}
}
const toSyntaxClasses = scopes =>
typeof scopes === 'string'
? scopes
.split('.')
.map(s => `syntax--${s}`)
.join(' ')
: Array.isArray(scopes)
? scopes.map(toSyntaxClasses)
: scopes.match
? {match: new RegExp(scopes.match), scopes: toSyntaxClasses(scopes.scopes)}
: Object.assign({}, scopes, {scopes: toSyntaxClasses(scopes.scopes)})
const preprocessScopes = value =>
typeof value === 'string'
? value
: Array.isArray(value)
? value.map(preprocessScopes)
: value.match
? {match: new RegExp(value.match), scopes: preprocessScopes(value.scopes)}
: Object.assign({}, value, {scopes: preprocessScopes(value.scopes)})
const NODE_NAME_REGEX = /[\w_]+/

View File

@@ -372,10 +372,10 @@ class TreeSitterLanguageMode {
const searchEndIndex = Math.max(0, endIndex - 1)
let smallestNode
this._forEachTreeWithRange(range, tree => {
this._forEachTreeWithRange(range, (tree, grammar) => {
let node = tree.rootNode.descendantForIndex(startIndex, searchEndIndex)
while (node) {
if (nodeContainsIndices(node, startIndex, endIndex) && where(node)) {
if (nodeContainsIndices(node, startIndex, endIndex) && where(node, grammar)) {
if (nodeIsSmaller(node, smallestNode)) smallestNode = node
break
}
@@ -396,9 +396,17 @@ class TreeSitterLanguageMode {
}
bufferRangeForScopeAtPosition (selector, position) {
const nodeCursorAdapter = new NodeCursorAdaptor()
if (typeof selector === 'string') {
const match = matcherForSelector(selector)
selector = ({type}) => match(type)
selector = (node, grammar) => {
const rules = grammar.scopeMap.get([node.type], [0], node.named)
nodeCursorAdapter.node = node
const scopeName = applyLeafRules(rules, nodeCursorAdapter)
if (scopeName != null) {
return match(scopeName)
}
}
}
if (selector === null) selector = undefined
const node = this.getSyntaxNodeAtPosition(position, selector)
@@ -425,10 +433,10 @@ class TreeSitterLanguageMode {
const iterator = this.buildHighlightIterator()
const scopes = []
for (const scope of iterator.seek(point)) {
scopes.push(this.grammar.scopeNameForScopeId(scope, false))
scopes.push(this.grammar.scopeNameForScopeId(scope))
}
for (const scope of iterator.getOpenScopeIds()) {
scopes.push(this.grammar.scopeNameForScopeId(scope, false))
scopes.push(this.grammar.scopeNameForScopeId(scope))
}
if (scopes.length === 0 || scopes[0] !== this.grammar.scopeName) {
scopes.unshift(this.grammar.scopeName)
@@ -743,14 +751,10 @@ class HighlightIterator {
iterator.languageLayer.tree.rootNode.endPosition
).toString()
)
console.log('close', iterator.closeTags.map(id => this.shortClassNameForScopeId(id)))
console.log('open', iterator.openTags.map(id => this.shortClassNameForScopeId(id)))
console.log('close', iterator.closeTags.map(id => this.languageMode.grammar.scopeNameForScopeId(id)))
console.log('open', iterator.openTags.map(id => this.languageMode.grammar.scopeNameForScopeId(id)))
}
}
shortClassNameForScopeId (id) {
return this.languageMode.classNameForScopeId(id).replace(/syntax--/g, '')
}
}
class LayerHighlightIterator {
@@ -944,14 +948,14 @@ class LayerHighlightIterator {
}
_currentScopeId () {
const rules = this.languageLayer.grammar.scopeMap.get(
const value = this.languageLayer.grammar.scopeMap.get(
this.containingNodeTypes,
this.containingNodeChildIndices,
this.treeCursor.nodeIsNamed
)
const scopes = applyLeafRules(rules, this.treeCursor)
if (scopes) {
return this.languageLayer.languageMode.grammar.idForScope(scopes)
const scopeName = applyLeafRules(value, this.treeCursor)
if (scopeName) {
return this.languageLayer.languageMode.grammar.idForScope(scopeName)
}
}
}
@@ -979,6 +983,12 @@ const applyLeafRules = (rules, cursor) => {
}
}
class NodeCursorAdaptor {
get nodeText () {
return this.node.text
}
}
class NullHighlightIterator {
seek () { return [] }
moveToSuccessor () {}