diff --git a/package.json b/package.json index 1b75fe321..d9e89e222 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ "temp": "^0.8.3", "text-buffer": "13.14.5", "timecop": "https://www.atom.io/api/packages/timecop/versions/0.36.2/tarball", - "tree-sitter": "0.13.0", + "tree-sitter": "0.13.2", "tree-view": "https://www.atom.io/api/packages/tree-view/versions/0.222.0/tarball", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.8", diff --git a/spec/tree-sitter-language-mode-spec.js b/spec/tree-sitter-language-mode-spec.js index 37413a9d5..9a574c5e8 100644 --- a/spec/tree-sitter-language-mode-spec.js +++ b/spec/tree-sitter-language-mode-spec.js @@ -314,6 +314,39 @@ describe('TreeSitterLanguageMode', () => { ]) }) + it('applies rules when specified', async () => { + const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { + parser: 'tree-sitter-javascript', + scopes: { + 'identifier': [ + {match: '^(exports|document|window|global)$', scopes: 'global'}, + {match: '^[A-Z_]+$', scopes: 'constant'}, + {match: '^[A-Z]', scopes: 'constructor'}, + 'variable' + ], + } + }) + + buffer.setText(`exports.object = Class(SOME_CONSTANT, x)`) + + const languageMode = new TreeSitterLanguageMode({buffer, grammar}) + buffer.setLanguageMode(languageMode) + await nextHighlightingUpdate(languageMode) + + expectTokensToEqual(editor, [ + [ + {text: 'exports', scopes: ['global']}, + {text: '.object = ', scopes: []}, + {text: 'Class', scopes: ['constructor']}, + {text: '(', scopes: []}, + {text: 'SOME_CONSTANT', scopes: ['constant']}, + {text: ', ', scopes: []}, + {text: 'x', scopes: ['variable']}, + {text: ')', scopes: []}, + ] + ]) + }) + it('handles nodes that start before their first child and end after their last child', async () => { const grammar = new TreeSitterGrammar(atom.grammars, rubyGrammarPath, { parser: 'tree-sitter-ruby', diff --git a/src/tree-sitter-grammar.js b/src/tree-sitter-grammar.js index 04ca7f438..8e99d07db 100644 --- a/src/tree-sitter-grammar.js +++ b/src/tree-sitter-grammar.js @@ -22,10 +22,7 @@ class TreeSitterGrammar { const scopeSelectors = {} for (const key in params.scopes || {}) { - scopeSelectors[key] = params.scopes[key] - .split('.') - .map(s => `syntax--${s}`) - .join(' ') + scopeSelectors[key] = toSyntaxClasses(params.scopes[key]) } this.scopeMap = new SyntaxScopeMap(scopeSelectors) @@ -74,6 +71,18 @@ 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 NODE_NAME_REGEX = /[\w_]+/ function matcherForSpec (spec) { diff --git a/src/tree-sitter-language-mode.js b/src/tree-sitter-language-mode.js index 2fec0843a..5026d3b86 100644 --- a/src/tree-sitter-language-mode.js +++ b/src/tree-sitter-language-mode.js @@ -870,7 +870,6 @@ class LayerHighlightIterator { } // Private methods - _moveUp (atLastChild) { let result = false const {endIndex} = this.treeCursor @@ -928,13 +927,37 @@ class LayerHighlightIterator { } _currentScopeId () { - const name = this.languageLayer.grammar.scopeMap.get( + const rules = this.languageLayer.grammar.scopeMap.get( this.containingNodeTypes, this.containingNodeChildIndices, this.treeCursor.nodeIsNamed ) - if (name) { - return this.languageLayer.languageMode.grammar.idForScope(name) + const scopes = applyLeafRules(rules, this.treeCursor) + if (scopes) { + return this.languageLayer.languageMode.grammar.idForScope(scopes) + } + } +} + +const applyLeafRules = (rules, cursor) => { + if (!rules || typeof rules === 'string') return rules + if (Array.isArray(rules)) { + for (let i = 0, {length} = rules; i !== length; ++i) { + const result = applyLeafRules(rules[i], cursor) + if (result) return result + } + return undefined + } + if (typeof rules === 'object') { + if (rules.exact) { + return cursor.nodeText === rules.exact + ? applyLeafRules(rules.scopes, cursor) + : undefined + } + if (rules.match) { + return rules.match.test(cursor.nodeText) + ? applyLeafRules(rules.scopes, cursor) + : undefined } } }