diff --git a/spec/tree-sitter-language-mode-spec.js b/spec/tree-sitter-language-mode-spec.js index e1b06dd2d..f2a10f3d9 100644 --- a/spec/tree-sitter-language-mode-spec.js +++ b/spec/tree-sitter-language-mode-spec.js @@ -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('\\${person\\.name}')) }) diff --git a/src/syntax-scope-map.js b/src/syntax-scope-map.js index e000fb647..fefee26d7 100644 --- a/src/syntax-scope-map.js +++ b/src/syntax-scope-map.js @@ -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 } } diff --git a/src/tree-sitter-grammar.js b/src/tree-sitter-grammar.js index fc572221a..80cbbe7aa 100644 --- a/src/tree-sitter-grammar.js +++ b/src/tree-sitter-grammar.js @@ -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_]+/ diff --git a/src/tree-sitter-language-mode.js b/src/tree-sitter-language-mode.js index 82160c013..edcf7ea59 100644 --- a/src/tree-sitter-language-mode.js +++ b/src/tree-sitter-language-mode.js @@ -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 () {}