From 8fa9a45a54acec7a2cb89a5543116bb2c5d937e0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jul 2018 13:43:10 -0700 Subject: [PATCH] Incorporated injected languages in scope descriptors --- spec/tree-sitter-language-mode-spec.js | 67 ++++++++++++++++++++++++-- src/tree-sitter-language-mode.js | 51 +++++++++++++++----- 2 files changed, 100 insertions(+), 18 deletions(-) diff --git a/spec/tree-sitter-language-mode-spec.js b/spec/tree-sitter-language-mode-spec.js index e2671e2ea..c23849d30 100644 --- a/spec/tree-sitter-language-mode-spec.js +++ b/spec/tree-sitter-language-mode-spec.js @@ -402,11 +402,7 @@ describe('TreeSitterLanguageMode', () => { attribute_name: 'attr' }, injectionRegExp: 'html', - injectionPoints: [{ - type: 'raw_element', - language () { return 'javascript' }, - content (node) { return node.child(1) } - }] + injectionPoints: [SCRIPT_TAG_INJECTION_POINT] }) }) @@ -1081,6 +1077,61 @@ describe('TreeSitterLanguageMode', () => { 'property_identifier' ]) }) + + it('includes nodes in injected syntax trees', async () => { + const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { + id: 'javascript', + parser: 'tree-sitter-javascript', + scopes: {}, + injectionRegExp: 'javascript', + injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT] + }) + + const htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, { + id: 'html', + parser: 'tree-sitter-html', + scopes: {}, + injectionRegExp: 'html', + injectionPoints: [SCRIPT_TAG_INJECTION_POINT] + }) + + atom.grammars.addGrammar(jsGrammar) + atom.grammars.addGrammar(htmlGrammar) + + buffer.setText(` +
+ +
+ `) + + const languageMode = new TreeSitterLanguageMode({buffer, grammar: htmlGrammar, grammars: atom.grammars}) + buffer.setLanguageMode(languageMode) + await nextHighlightingUpdate(languageMode) + await nextHighlightingUpdate(languageMode) + await nextHighlightingUpdate(languageMode) + + 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' + ]) + }) }) describe('TextEditor.selectLargerSyntaxNode and .selectSmallerSyntaxNode', () => { @@ -1247,3 +1298,9 @@ const HTML_TEMPLATE_LITERAL_INJECTION_POINT = { return node.lastChild } } + +const SCRIPT_TAG_INJECTION_POINT = { + type: 'raw_element', + language () { return 'javascript' }, + content (node) { return node.child(1) } +} diff --git a/src/tree-sitter-language-mode.js b/src/tree-sitter-language-mode.js index c431e51b9..7d0377f28 100644 --- a/src/tree-sitter-language-mode.js +++ b/src/tree-sitter-language-mode.js @@ -389,23 +389,41 @@ class TreeSitterLanguageMode { 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 iterators = [] + this._forEachTreeWithRange(new Range(point, point), tree => { + const rootStartIndex = tree.rootNode.startIndex + let node = tree.rootNode.descendantForPosition(point) - const result = [] - while (node) { - result.push(node.type) - node = node.parent + // 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 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() + } } - result.push(this.grammar.id) - return new ScopeDescriptor({scopes: result.reverse()}) + + scopes.push(this.grammar.id) + return new ScopeDescriptor({scopes: scopes.reverse()}) } getGrammar () { @@ -1011,6 +1029,13 @@ 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 pointIsGreater (left, right) { return left.row > right.row || left.row === right.row && left.column > right.column }