2017-12-01 04:13:30 +03:00
|
|
|
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
|
|
|
|
|
|
|
const dedent = require('dedent')
|
|
|
|
const TextBuffer = require('text-buffer')
|
2018-01-03 20:34:12 +03:00
|
|
|
const {Point} = TextBuffer
|
2017-12-01 04:13:30 +03:00
|
|
|
const TextEditor = require('../src/text-editor')
|
|
|
|
const TreeSitterGrammar = require('../src/tree-sitter-grammar')
|
|
|
|
const TreeSitterLanguageMode = require('../src/tree-sitter-language-mode')
|
|
|
|
|
2017-12-06 22:09:44 +03:00
|
|
|
const cGrammarPath = require.resolve('language-c/grammars/tree-sitter-c.cson')
|
|
|
|
const pythonGrammarPath = require.resolve('language-python/grammars/tree-sitter-python.cson')
|
2017-12-01 04:13:30 +03:00
|
|
|
const jsGrammarPath = require.resolve('language-javascript/grammars/tree-sitter-javascript.cson')
|
2018-06-19 00:27:49 +03:00
|
|
|
const htmlGrammarPath = require.resolve('language-html/grammars/tree-sitter-html.cson')
|
2018-07-06 23:53:13 +03:00
|
|
|
const ejsGrammarPath = require.resolve('language-html/grammars/tree-sitter-ejs.cson')
|
2018-07-20 03:15:04 +03:00
|
|
|
const rubyGrammarPath = require.resolve('language-ruby/grammars/tree-sitter-ruby.cson')
|
2017-12-01 04:13:30 +03:00
|
|
|
|
|
|
|
describe('TreeSitterLanguageMode', () => {
|
|
|
|
let editor, buffer
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
editor = await atom.workspace.open('')
|
|
|
|
buffer = editor.getBuffer()
|
2018-07-16 20:38:37 +03:00
|
|
|
editor.displayLayer.reset({foldCharacter: '…'})
|
2017-12-01 04:13:30 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
describe('highlighting', () => {
|
2018-05-16 03:53:47 +03:00
|
|
|
it('applies the most specific scope mapping to each node in the syntax tree', async () => {
|
2017-12-04 21:18:38 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2017-12-01 04:13:30 +03:00
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'program': 'source',
|
|
|
|
'call_expression > identifier': 'function',
|
|
|
|
'property_identifier': 'property',
|
|
|
|
'call_expression > member_expression > property_identifier': 'method'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2018-06-27 23:36:17 +03:00
|
|
|
buffer.setText('aa.bbb = cc(d.eee());')
|
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
2017-12-15 20:44:45 +03:00
|
|
|
expectTokensToEqual(editor, [[
|
2017-12-01 04:13:30 +03:00
|
|
|
{text: 'aa.', scopes: ['source']},
|
|
|
|
{text: 'bbb', scopes: ['source', 'property']},
|
|
|
|
{text: ' = ', scopes: ['source']},
|
|
|
|
{text: 'cc', scopes: ['source', 'function']},
|
|
|
|
{text: '(d.', scopes: ['source']},
|
|
|
|
{text: 'eee', scopes: ['source', 'method']},
|
|
|
|
{text: '());', scopes: ['source']}
|
2017-12-15 20:44:45 +03:00
|
|
|
]])
|
2017-12-04 21:18:38 +03:00
|
|
|
})
|
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
it('can start or end multiple scopes at the same position', async () => {
|
2017-12-04 21:18:38 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'program': 'source',
|
|
|
|
'call_expression': 'call',
|
|
|
|
'member_expression': 'member',
|
|
|
|
'identifier': 'variable',
|
|
|
|
'"("': 'open-paren',
|
|
|
|
'")"': 'close-paren',
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2018-06-27 23:36:17 +03:00
|
|
|
buffer.setText('a = bb.ccc();')
|
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
2017-12-15 20:44:45 +03:00
|
|
|
expectTokensToEqual(editor, [[
|
2017-12-04 21:18:38 +03:00
|
|
|
{text: 'a', scopes: ['source', 'variable']},
|
|
|
|
{text: ' = ', scopes: ['source']},
|
|
|
|
{text: 'bb', scopes: ['source', 'call', 'member', 'variable']},
|
|
|
|
{text: '.ccc', scopes: ['source', 'call', 'member']},
|
|
|
|
{text: '(', scopes: ['source', 'call', 'open-paren']},
|
|
|
|
{text: ')', scopes: ['source', 'call', 'close-paren']},
|
|
|
|
{text: ';', scopes: ['source']}
|
2017-12-15 20:44:45 +03:00
|
|
|
]])
|
|
|
|
})
|
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
it('can resume highlighting on a line that starts with whitespace', async () => {
|
2017-12-15 20:44:45 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'call_expression > member_expression > property_identifier': 'function',
|
|
|
|
'property_identifier': 'member',
|
|
|
|
'identifier': 'variable'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2018-06-27 23:36:17 +03:00
|
|
|
buffer.setText('a\n .b();')
|
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
2017-12-15 20:44:45 +03:00
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'a', scopes: ['variable']},
|
|
|
|
],
|
|
|
|
[
|
2018-07-16 20:38:37 +03:00
|
|
|
{text: ' ', scopes: ['leading-whitespace']},
|
2017-12-15 20:44:45 +03:00
|
|
|
{text: '.', scopes: []},
|
|
|
|
{text: 'b', scopes: ['function']},
|
|
|
|
{text: '();', scopes: []}
|
|
|
|
]
|
2017-12-04 21:18:38 +03:00
|
|
|
])
|
2017-12-01 04:13:30 +03:00
|
|
|
})
|
2018-01-03 20:34:12 +03:00
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
it('correctly skips over tokens with zero size', async () => {
|
2018-08-18 20:35:41 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, cGrammarPath, {
|
2018-01-03 20:34:12 +03:00
|
|
|
parser: 'tree-sitter-c',
|
|
|
|
scopes: {
|
|
|
|
'primitive_type': 'type',
|
|
|
|
'identifier': 'variable',
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2018-06-27 23:36:17 +03:00
|
|
|
buffer.setText('int main() {\n int a\n int b;\n}');
|
|
|
|
|
2018-01-03 20:34:12 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
expect(
|
2018-05-16 03:53:47 +03:00
|
|
|
languageMode.tree.rootNode.descendantForPosition(Point(1, 2), Point(1, 6)).toString()
|
2018-01-03 20:34:12 +03:00
|
|
|
).toBe('(declaration (primitive_type) (identifier) (MISSING))')
|
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'int', scopes: ['type']},
|
|
|
|
{text: ' ', scopes: []},
|
|
|
|
{text: 'main', scopes: ['variable']},
|
|
|
|
{text: '() {', scopes: []}
|
|
|
|
],
|
|
|
|
[
|
2018-07-16 20:38:37 +03:00
|
|
|
{text: ' ', scopes: ['leading-whitespace']},
|
2018-01-03 20:34:12 +03:00
|
|
|
{text: 'int', scopes: ['type']},
|
|
|
|
{text: ' ', scopes: []},
|
|
|
|
{text: 'a', scopes: ['variable']}
|
|
|
|
],
|
|
|
|
[
|
2018-07-16 20:38:37 +03:00
|
|
|
{text: ' ', scopes: ['leading-whitespace']},
|
2018-01-03 20:34:12 +03:00
|
|
|
{text: 'int', scopes: ['type']},
|
|
|
|
{text: ' ', scopes: []},
|
|
|
|
{text: 'b', scopes: ['variable']},
|
|
|
|
{text: ';', scopes: []}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '}', scopes: []}
|
|
|
|
]
|
|
|
|
])
|
|
|
|
})
|
2018-01-26 01:02:31 +03:00
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
it('updates lines\' highlighting when they are affected by distant changes', async () => {
|
2018-01-26 01:02:31 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'call_expression > identifier': 'function',
|
|
|
|
'property_identifier': 'member'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2018-06-27 23:36:17 +03:00
|
|
|
buffer.setText('a(\nb,\nc\n')
|
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
2018-01-26 01:02:31 +03:00
|
|
|
|
|
|
|
// missing closing paren
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[{text: 'a(', scopes: []}],
|
|
|
|
[{text: 'b,', scopes: []}],
|
|
|
|
[{text: 'c', scopes: []}],
|
|
|
|
[{text: '', scopes: []}]
|
|
|
|
])
|
|
|
|
|
|
|
|
buffer.append(')')
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'a', scopes: ['function']},
|
|
|
|
{text: '(', scopes: []}
|
|
|
|
],
|
|
|
|
[{text: 'b,', scopes: []}],
|
|
|
|
[{text: 'c', scopes: []}],
|
|
|
|
[{text: ')', scopes: []}]
|
|
|
|
])
|
|
|
|
})
|
2018-02-14 18:49:02 +03:00
|
|
|
|
2018-08-04 02:47:04 +03:00
|
|
|
it('allows comma-separated selectors as scope mapping keys', async () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'identifier, call_expression > identifier': [
|
|
|
|
{match: '^[A-Z]', scopes: 'constructor'}
|
|
|
|
],
|
|
|
|
|
|
|
|
'call_expression > identifier': 'function'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(`a(B(new C))`)
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'a', scopes: ['function']},
|
|
|
|
{text: '(', scopes: []},
|
|
|
|
{text: 'B', scopes: ['constructor']},
|
|
|
|
{text: '(new ', scopes: []},
|
|
|
|
{text: 'C', scopes: ['constructor']},
|
|
|
|
{text: '))', scopes: []},
|
|
|
|
]
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
it('handles edits after tokens that end between CR and LF characters (regression)', async () => {
|
2018-02-14 18:49:02 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'comment': 'comment',
|
|
|
|
'string': 'string',
|
|
|
|
'property_identifier': 'property',
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText([
|
|
|
|
'// abc',
|
|
|
|
'',
|
|
|
|
'a("b").c'
|
|
|
|
].join('\r\n'))
|
2018-06-27 23:36:17 +03:00
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
2018-02-14 18:49:02 +03:00
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[{text: '// abc', scopes: ['comment']}],
|
|
|
|
[{text: '', scopes: []}],
|
|
|
|
[
|
|
|
|
{text: 'a(', scopes: []},
|
|
|
|
{text: '"b"', scopes: ['string']},
|
|
|
|
{text: ').', scopes: []},
|
|
|
|
{text: 'c', scopes: ['property']}
|
|
|
|
]
|
|
|
|
])
|
|
|
|
|
|
|
|
buffer.insert([2, 0], ' ')
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[{text: '// abc', scopes: ['comment']}],
|
|
|
|
[{text: '', scopes: []}],
|
|
|
|
[
|
2018-07-16 20:38:37 +03:00
|
|
|
{text: ' ', scopes: ['leading-whitespace']},
|
2018-02-14 18:49:02 +03:00
|
|
|
{text: 'a(', scopes: []},
|
|
|
|
{text: '"b"', scopes: ['string']},
|
|
|
|
{text: ').', scopes: []},
|
|
|
|
{text: 'c', scopes: ['property']}
|
|
|
|
]
|
|
|
|
])
|
|
|
|
})
|
2018-05-22 21:12:40 +03:00
|
|
|
|
2018-06-29 02:34:47 +03:00
|
|
|
it('handles multi-line nodes with children on different lines (regression)', async () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'template_string': 'string',
|
|
|
|
'"${"': 'interpolation',
|
|
|
|
'"}"': 'interpolation'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
buffer.setText('`\na${1}\nb${2}\n`;')
|
2018-07-13 23:03:56 +03:00
|
|
|
|
2018-06-29 02:34:47 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: '`', scopes: ['string']}
|
|
|
|
], [
|
|
|
|
{text: 'a', scopes: ['string']},
|
|
|
|
{text: '${', scopes: ['string', 'interpolation']},
|
|
|
|
{text: '1', scopes: ['string']},
|
|
|
|
{text: '}', scopes: ['string', 'interpolation']}
|
|
|
|
], [
|
|
|
|
{text: 'b', scopes: ['string']},
|
|
|
|
{text: '${', scopes: ['string', 'interpolation']},
|
|
|
|
{text: '2', scopes: ['string']},
|
|
|
|
{text: '}', scopes: ['string', 'interpolation']}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '`', scopes: ['string']},
|
|
|
|
{text: ';', scopes: []}
|
|
|
|
]
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
2018-07-16 20:38:37 +03:00
|
|
|
it('handles folds inside of highlighted tokens', async () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'comment': 'comment',
|
|
|
|
'call_expression > identifier': 'function',
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
/*
|
|
|
|
* Hello
|
|
|
|
*/
|
|
|
|
|
|
|
|
hello();
|
|
|
|
`)
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
editor.foldBufferRange([[0, 2], [2, 0]])
|
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: '/*', scopes: ['comment']},
|
|
|
|
{text: '…', scopes: ['fold-marker']},
|
|
|
|
{text: ' */', scopes: ['comment']}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '', scopes: []}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: 'hello', scopes: ['function']},
|
|
|
|
{text: '();', scopes: []},
|
|
|
|
]
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
2018-08-04 02:47:04 +03:00
|
|
|
it('applies regex match rules when specified', async () => {
|
2018-07-24 20:34:41 +03:00
|
|
|
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)
|
|
|
|
|
|
|
|
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: []},
|
|
|
|
]
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
2018-07-20 20:15:30 +03:00
|
|
|
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',
|
|
|
|
scopes: {
|
|
|
|
'bare_string': 'string',
|
|
|
|
'interpolation': 'embedded',
|
|
|
|
'"#{"': 'punctuation',
|
|
|
|
'"}"': 'punctuation',
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// The bare string node `bc#{d}ef` has one child: the interpolation, and that child
|
|
|
|
// starts later and ends earlier than the bare string.
|
|
|
|
buffer.setText('a = %W( bc#{d}ef )')
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'a = %W( ', scopes: []},
|
|
|
|
{text: 'bc', scopes: ['string']},
|
|
|
|
{text: '#{', scopes: ['string', 'embedded', 'punctuation']},
|
|
|
|
{text: 'd', scopes: ['string', 'embedded']},
|
|
|
|
{text: '}', scopes: ['string', 'embedded', 'punctuation']},
|
|
|
|
{text: 'ef', scopes: ['string']},
|
|
|
|
{text: ' )', scopes: []},
|
|
|
|
]
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
2018-05-22 21:12:40 +03:00
|
|
|
describe('when the buffer changes during a parse', () => {
|
|
|
|
it('immediately parses again when the current parse completes', async () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'identifier': 'variable',
|
|
|
|
'call_expression > identifier': 'function',
|
|
|
|
'new_expression > call_expression > identifier': 'constructor'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText('abc;');
|
2018-06-27 23:36:17 +03:00
|
|
|
|
2018-08-24 19:33:58 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar, syncOperationLimit: 0})
|
2018-06-27 23:36:17 +03:00
|
|
|
buffer.setLanguageMode(languageMode)
|
2018-07-13 23:03:56 +03:00
|
|
|
await nextHighlightingUpdate(languageMode)
|
|
|
|
await new Promise(process.nextTick)
|
2018-06-27 23:36:17 +03:00
|
|
|
|
2018-05-22 21:12:40 +03:00
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'abc', scopes: ['variable']},
|
|
|
|
{text: ';', scopes: []}
|
|
|
|
],
|
|
|
|
])
|
|
|
|
|
|
|
|
buffer.setTextInRange([[0, 3], [0, 3]], '()');
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'abc()', scopes: ['variable']},
|
|
|
|
{text: ';', scopes: []}
|
|
|
|
],
|
|
|
|
])
|
|
|
|
|
|
|
|
buffer.setTextInRange([[0, 0], [0, 0]], 'new ');
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'new ', scopes: []},
|
|
|
|
{text: 'abc()', scopes: ['variable']},
|
|
|
|
{text: ';', scopes: []}
|
|
|
|
],
|
|
|
|
])
|
|
|
|
|
2018-07-13 23:03:56 +03:00
|
|
|
await nextHighlightingUpdate(languageMode)
|
2018-05-22 21:12:40 +03:00
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'new ', scopes: []},
|
|
|
|
{text: 'abc', scopes: ['function']},
|
|
|
|
{text: '();', scopes: []}
|
|
|
|
],
|
|
|
|
])
|
|
|
|
|
2018-07-13 23:03:56 +03:00
|
|
|
await nextHighlightingUpdate(languageMode)
|
2018-05-22 21:12:40 +03:00
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'new ', scopes: []},
|
|
|
|
{text: 'abc', scopes: ['constructor']},
|
|
|
|
{text: '();', scopes: []}
|
|
|
|
],
|
|
|
|
])
|
|
|
|
})
|
|
|
|
})
|
2018-06-22 03:06:55 +03:00
|
|
|
|
2018-08-28 00:54:08 +03:00
|
|
|
describe('when changes are small enough to be re-parsed synchronously', () => {
|
|
|
|
it('can incorporate multiple consecutive synchronous updates', () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'property_identifier': 'property',
|
|
|
|
'call_expression > identifier': 'function',
|
|
|
|
'call_expression > member_expression > property_identifier': 'method',
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
buffer.setText('a');
|
|
|
|
expectTokensToEqual(editor, [[
|
|
|
|
{text: 'a', scopes: []},
|
|
|
|
]])
|
|
|
|
|
|
|
|
buffer.append('.')
|
|
|
|
expectTokensToEqual(editor, [[
|
|
|
|
{text: 'a.', scopes: []},
|
|
|
|
]])
|
|
|
|
|
|
|
|
buffer.append('b')
|
|
|
|
expectTokensToEqual(editor, [[
|
|
|
|
{text: 'a.', scopes: []},
|
|
|
|
{text: 'b', scopes: ['property']},
|
|
|
|
]])
|
|
|
|
|
|
|
|
buffer.append('()')
|
|
|
|
expectTokensToEqual(editor, [[
|
|
|
|
{text: 'a.', scopes: []},
|
|
|
|
{text: 'b', scopes: ['method']},
|
|
|
|
{text: '()', scopes: []},
|
|
|
|
]])
|
|
|
|
|
|
|
|
buffer.delete([[0, 1], [0, 2]])
|
|
|
|
expectTokensToEqual(editor, [[
|
|
|
|
{text: 'ab', scopes: ['function']},
|
|
|
|
{text: '()', scopes: []},
|
|
|
|
]])
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2018-06-27 23:36:17 +03:00
|
|
|
describe('injectionPoints and injectionPatterns', () => {
|
2018-06-27 01:30:03 +03:00
|
|
|
let jsGrammar, htmlGrammar
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'javascript',
|
2018-06-22 03:06:55 +03:00
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'property_identifier': 'property',
|
|
|
|
'call_expression > identifier': 'function',
|
2018-06-27 01:30:03 +03:00
|
|
|
'template_string': 'string',
|
|
|
|
'template_substitution > "${"': 'interpolation',
|
|
|
|
'template_substitution > "}"': 'interpolation'
|
2018-06-22 03:06:55 +03:00
|
|
|
},
|
2018-06-29 22:53:43 +03:00
|
|
|
injectionRegExp: 'javascript',
|
2018-07-09 20:43:17 +03:00
|
|
|
injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT]
|
2018-06-22 03:06:55 +03:00
|
|
|
})
|
|
|
|
|
2018-06-27 01:30:03 +03:00
|
|
|
htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'html',
|
2018-06-22 03:06:55 +03:00
|
|
|
parser: 'tree-sitter-html',
|
|
|
|
scopes: {
|
2018-06-27 01:30:03 +03:00
|
|
|
fragment: 'html',
|
2018-06-22 03:06:55 +03:00
|
|
|
tag_name: 'tag',
|
|
|
|
attribute_name: 'attr'
|
|
|
|
},
|
2018-06-29 22:53:43 +03:00
|
|
|
injectionRegExp: 'html',
|
2018-07-16 23:43:10 +03:00
|
|
|
injectionPoints: [SCRIPT_TAG_INJECTION_POINT]
|
2018-06-22 03:06:55 +03:00
|
|
|
})
|
2018-06-27 01:30:03 +03:00
|
|
|
})
|
2018-06-22 03:06:55 +03:00
|
|
|
|
2018-06-27 01:30:03 +03:00
|
|
|
it('highlights code inside of injection points', async () => {
|
2018-06-30 00:41:20 +03:00
|
|
|
atom.grammars.addGrammar(jsGrammar)
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
2018-06-28 01:40:06 +03:00
|
|
|
buffer.setText('node.innerHTML = html `\na ${b}<img src="d">\n`;')
|
2018-06-27 23:36:17 +03:00
|
|
|
|
2018-06-22 03:06:55 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar: jsGrammar, grammars: atom.grammars})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'node.', scopes: []},
|
|
|
|
{text: 'innerHTML', scopes: ['property']},
|
|
|
|
{text: ' = ', scopes: []},
|
|
|
|
{text: 'html', scopes: ['function']},
|
|
|
|
{text: ' ', scopes: []},
|
2018-06-27 01:30:03 +03:00
|
|
|
{text: '`', scopes: ['string']},
|
2018-06-28 01:40:06 +03:00
|
|
|
{text: '', scopes: ['string', 'html']}
|
|
|
|
], [
|
2018-06-27 01:30:03 +03:00
|
|
|
{text: 'a ', scopes: ['string', 'html']},
|
|
|
|
{text: '${', scopes: ['string', 'html', 'interpolation']},
|
|
|
|
{text: 'b', scopes: ['string', 'html']},
|
|
|
|
{text: '}', scopes: ['string', 'html', 'interpolation']},
|
|
|
|
{text: '<', scopes: ['string', 'html']},
|
|
|
|
{text: 'img', scopes: ['string', 'html', 'tag']},
|
|
|
|
{text: ' ', scopes: ['string', 'html']},
|
|
|
|
{text: 'src', scopes: ['string', 'html', 'attr']},
|
2018-06-28 01:40:06 +03:00
|
|
|
{text: '="d">', scopes: ['string', 'html']}
|
|
|
|
], [
|
|
|
|
{text: '`', scopes: ['string']},
|
|
|
|
{text: ';', scopes: []},
|
|
|
|
],
|
|
|
|
])
|
|
|
|
|
|
|
|
const range = buffer.findSync('html')
|
|
|
|
buffer.setTextInRange(range, 'xml')
|
2018-07-13 23:03:56 +03:00
|
|
|
await nextHighlightingUpdate(languageMode)
|
2018-06-28 01:40:06 +03:00
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'node.', scopes: []},
|
|
|
|
{text: 'innerHTML', scopes: ['property']},
|
|
|
|
{text: ' = ', scopes: []},
|
|
|
|
{text: 'xml', scopes: ['function']},
|
|
|
|
{text: ' ', scopes: []},
|
|
|
|
{text: '`', scopes: ['string']}
|
|
|
|
], [
|
|
|
|
{text: 'a ', scopes: ['string']},
|
|
|
|
{text: '${', scopes: ['string', 'interpolation']},
|
|
|
|
{text: 'b', scopes: ['string']},
|
|
|
|
{text: '}', scopes: ['string', 'interpolation']},
|
|
|
|
{text: '<img src="d">', scopes: ['string']},
|
2018-06-27 01:30:03 +03:00
|
|
|
], [
|
|
|
|
{text: '`', scopes: ['string']},
|
2018-06-22 03:06:55 +03:00
|
|
|
{text: ';', scopes: []},
|
|
|
|
],
|
|
|
|
])
|
|
|
|
})
|
2018-06-29 22:53:43 +03:00
|
|
|
|
|
|
|
it('highlights the content after injections', async () => {
|
2018-06-30 00:41:20 +03:00
|
|
|
atom.grammars.addGrammar(jsGrammar)
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
2018-06-29 22:53:43 +03:00
|
|
|
buffer.setText('<script>\nhello();\n</script>\n<div>\n</div>')
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar: htmlGrammar, grammars: atom.grammars})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: '<', scopes: ['html']},
|
|
|
|
{text: 'script', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']},
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: 'hello', scopes: ['html', 'function']},
|
|
|
|
{text: '();', scopes: ['html']},
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '</', scopes: ['html']},
|
|
|
|
{text: 'script', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']},
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '<', scopes: ['html']},
|
|
|
|
{text: 'div', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']},
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '</', scopes: ['html']},
|
|
|
|
{text: 'div', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']},
|
|
|
|
]
|
|
|
|
])
|
|
|
|
})
|
2018-06-30 00:41:20 +03:00
|
|
|
|
|
|
|
it('updates buffers highlighting when a grammar with injectionRegExp is added', async () => {
|
|
|
|
atom.grammars.addGrammar(jsGrammar)
|
|
|
|
|
|
|
|
buffer.setText('node.innerHTML = html `\na ${b}<img src="d">\n`;')
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar: jsGrammar, grammars: atom.grammars})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
2018-07-13 23:03:56 +03:00
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
2018-06-30 00:41:20 +03:00
|
|
|
[
|
|
|
|
{text: 'node.', scopes: []},
|
|
|
|
{text: 'innerHTML', scopes: ['property']},
|
|
|
|
{text: ' = ', scopes: []},
|
|
|
|
{text: 'html', scopes: ['function']},
|
|
|
|
{text: ' ', scopes: []},
|
|
|
|
{text: '`', scopes: ['string']}
|
|
|
|
], [
|
|
|
|
{text: 'a ', scopes: ['string']},
|
|
|
|
{text: '${', scopes: ['string', 'interpolation']},
|
|
|
|
{text: 'b', scopes: ['string']},
|
|
|
|
{text: '}', scopes: ['string', 'interpolation']},
|
|
|
|
{text: '<img src="d">', scopes: ['string']},
|
|
|
|
], [
|
|
|
|
{text: '`', scopes: ['string']},
|
|
|
|
{text: ';', scopes: []},
|
|
|
|
],
|
|
|
|
])
|
|
|
|
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
2018-07-13 23:03:56 +03:00
|
|
|
await nextHighlightingUpdate(languageMode)
|
2018-06-30 00:41:20 +03:00
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: 'node.', scopes: []},
|
|
|
|
{text: 'innerHTML', scopes: ['property']},
|
|
|
|
{text: ' = ', scopes: []},
|
|
|
|
{text: 'html', scopes: ['function']},
|
|
|
|
{text: ' ', scopes: []},
|
|
|
|
{text: '`', scopes: ['string']},
|
|
|
|
{text: '', scopes: ['string', 'html']}
|
|
|
|
], [
|
|
|
|
{text: 'a ', scopes: ['string', 'html']},
|
|
|
|
{text: '${', scopes: ['string', 'html', 'interpolation']},
|
|
|
|
{text: 'b', scopes: ['string', 'html']},
|
|
|
|
{text: '}', scopes: ['string', 'html', 'interpolation']},
|
|
|
|
{text: '<', scopes: ['string', 'html']},
|
|
|
|
{text: 'img', scopes: ['string', 'html', 'tag']},
|
|
|
|
{text: ' ', scopes: ['string', 'html']},
|
|
|
|
{text: 'src', scopes: ['string', 'html', 'attr']},
|
|
|
|
{text: '="d">', scopes: ['string', 'html']}
|
|
|
|
], [
|
|
|
|
{text: '`', scopes: ['string']},
|
|
|
|
{text: ';', scopes: []},
|
|
|
|
],
|
|
|
|
])
|
|
|
|
})
|
2018-07-06 23:53:13 +03:00
|
|
|
|
|
|
|
it('handles injections that intersect', async () => {
|
|
|
|
const ejsGrammar = new TreeSitterGrammar(atom.grammars, ejsGrammarPath, {
|
|
|
|
id: 'ejs',
|
|
|
|
parser: 'tree-sitter-embedded-template',
|
|
|
|
scopes: {
|
|
|
|
'"<%="': 'directive',
|
|
|
|
'"%>"': 'directive',
|
|
|
|
},
|
|
|
|
injectionPoints: [
|
|
|
|
{
|
|
|
|
type: 'template',
|
|
|
|
language (node) { return 'javascript' },
|
|
|
|
content (node) { return node.descendantsOfType('code') }
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'template',
|
|
|
|
language (node) { return 'html' },
|
|
|
|
content (node) { return node.descendantsOfType('content') }
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
atom.grammars.addGrammar(jsGrammar)
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
|
|
|
|
|
|
|
buffer.setText('<body>\n<script>\nb(<%= c.d %>)\n</script>\n</body>')
|
2018-08-24 19:33:58 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({
|
|
|
|
buffer,
|
|
|
|
grammar: ejsGrammar,
|
|
|
|
grammars: atom.grammars,
|
|
|
|
})
|
2018-07-06 23:53:13 +03:00
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: '<', scopes: ['html']},
|
|
|
|
{text: 'body', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '<', scopes: ['html']},
|
|
|
|
{text: 'script', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: 'b', scopes: ['html', 'function']},
|
|
|
|
{text: '(', scopes: ['html']},
|
|
|
|
{text: '<%=', scopes: ['html', 'directive']},
|
|
|
|
{text: ' c.', scopes: ['html']},
|
|
|
|
{text: 'd', scopes: ['html', 'property']},
|
|
|
|
{text: ' ', scopes: ['html']},
|
|
|
|
{text: '%>', scopes: ['html', 'directive']},
|
|
|
|
{text: ')', scopes: ['html']},
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '</', scopes: ['html']},
|
|
|
|
{text: 'script', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']}
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '</', scopes: ['html']},
|
|
|
|
{text: 'body', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']}
|
|
|
|
],
|
|
|
|
])
|
|
|
|
})
|
2018-08-08 03:08:06 +03:00
|
|
|
|
|
|
|
it('notifies onDidTokenize listeners the first time all syntax highlighting is done', async () => {
|
|
|
|
const promise = new Promise(resolve => {
|
|
|
|
editor.onDidTokenize(event => {
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
[
|
|
|
|
{text: '<', scopes: ['html']},
|
|
|
|
{text: 'script', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']},
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: 'hello', scopes: ['html', 'function']},
|
|
|
|
{text: '();', scopes: ['html']},
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{text: '</', scopes: ['html']},
|
|
|
|
{text: 'script', scopes: ['html', 'tag']},
|
|
|
|
{text: '>', scopes: ['html']},
|
|
|
|
]
|
|
|
|
])
|
|
|
|
resolve()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
atom.grammars.addGrammar(jsGrammar)
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
|
|
|
buffer.setText('<script>\nhello();\n</script>')
|
|
|
|
|
2018-08-24 19:33:58 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({
|
|
|
|
buffer,
|
|
|
|
grammar: htmlGrammar,
|
|
|
|
grammars: atom.grammars,
|
|
|
|
syncOperationLimit: 0
|
|
|
|
})
|
2018-08-08 03:08:06 +03:00
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
await promise
|
|
|
|
})
|
2018-06-22 03:06:55 +03:00
|
|
|
})
|
2017-12-01 04:13:30 +03:00
|
|
|
})
|
2017-12-04 22:02:24 +03:00
|
|
|
|
|
|
|
describe('folding', () => {
|
2018-05-16 03:53:47 +03:00
|
|
|
it('can fold nodes that start and end with specified tokens', async () => {
|
2017-12-04 22:02:24 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
2017-12-05 23:39:52 +03:00
|
|
|
folds: [
|
|
|
|
{
|
|
|
|
start: {type: '{', index: 0},
|
|
|
|
end: {type: '}', index: -1}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
start: {type: '(', index: 0},
|
|
|
|
end: {type: ')', index: -1}
|
|
|
|
}
|
|
|
|
]
|
2017-12-04 22:02:24 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
module.exports =
|
|
|
|
class A {
|
|
|
|
getB (c,
|
|
|
|
d,
|
|
|
|
e) {
|
2017-12-05 23:39:52 +03:00
|
|
|
return this.f(g)
|
2017-12-04 22:02:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
`)
|
2018-06-27 23:36:17 +03:00
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
2017-12-04 22:02:24 +03:00
|
|
|
|
|
|
|
expect(editor.isFoldableAtBufferRow(0)).toBe(false)
|
|
|
|
expect(editor.isFoldableAtBufferRow(1)).toBe(true)
|
|
|
|
expect(editor.isFoldableAtBufferRow(2)).toBe(true)
|
|
|
|
expect(editor.isFoldableAtBufferRow(3)).toBe(false)
|
|
|
|
expect(editor.isFoldableAtBufferRow(4)).toBe(true)
|
2017-12-06 22:09:44 +03:00
|
|
|
expect(editor.isFoldableAtBufferRow(5)).toBe(false)
|
2017-12-04 22:02:24 +03:00
|
|
|
|
|
|
|
editor.foldBufferRow(2)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
module.exports =
|
|
|
|
class A {
|
2018-07-20 03:15:04 +03:00
|
|
|
getB (c,…) {
|
2017-12-05 23:39:52 +03:00
|
|
|
return this.f(g)
|
2017-12-04 22:02:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.foldBufferRow(4)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
module.exports =
|
|
|
|
class A {
|
2018-07-20 03:15:04 +03:00
|
|
|
getB (c,…) {…}
|
2017-12-04 22:02:24 +03:00
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
2017-12-04 22:40:44 +03:00
|
|
|
|
2018-07-20 20:43:05 +03:00
|
|
|
it('folds entire buffer rows when necessary to keep words on separate lines', async () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
folds: [
|
|
|
|
{
|
|
|
|
start: {type: '{', index: 0},
|
|
|
|
end: {type: '}', index: -1}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
start: {type: '(', index: 0},
|
|
|
|
end: {type: ')', index: -1}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
if (a) {
|
|
|
|
b
|
|
|
|
} else if (c) {
|
|
|
|
d
|
|
|
|
} else {
|
|
|
|
e
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
// Avoid bringing the `else if...` up onto the same screen line as the preceding `if`.
|
|
|
|
editor.foldBufferRow(1)
|
|
|
|
editor.foldBufferRow(3)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
if (a) {…
|
|
|
|
} else if (c) {…
|
|
|
|
} else {
|
|
|
|
e
|
2017-12-04 22:02:24 +03:00
|
|
|
}
|
|
|
|
`)
|
2018-07-20 20:43:05 +03:00
|
|
|
|
|
|
|
// It's ok to bring the final `}` onto the same screen line as the preceding `else`.
|
|
|
|
editor.foldBufferRow(5)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
if (a) {…
|
|
|
|
} else if (c) {…
|
|
|
|
} else {…}
|
|
|
|
`)
|
2017-12-04 22:02:24 +03:00
|
|
|
})
|
2017-12-04 22:40:44 +03:00
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
it('can fold nodes of specified types', async () => {
|
2017-12-05 23:39:52 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
folds: [
|
2017-12-06 22:09:44 +03:00
|
|
|
// Start the fold after the first child (the opening tag) and end it at the last child
|
|
|
|
// (the closing tag).
|
2017-12-05 23:39:52 +03:00
|
|
|
{
|
|
|
|
type: 'jsx_element',
|
2017-12-06 22:09:44 +03:00
|
|
|
start: {index: 0},
|
|
|
|
end: {index: -1}
|
2017-12-05 23:39:52 +03:00
|
|
|
},
|
2017-12-06 22:09:44 +03:00
|
|
|
|
|
|
|
// End the fold at the *second* to last child of the self-closing tag: the `/`.
|
2017-12-05 23:39:52 +03:00
|
|
|
{
|
|
|
|
type: 'jsx_self_closing_element',
|
|
|
|
start: {index: 1},
|
2017-12-06 22:09:44 +03:00
|
|
|
end: {index: -2}
|
|
|
|
}
|
2017-12-05 23:39:52 +03:00
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
const element1 = <Element
|
|
|
|
className='submit'
|
|
|
|
id='something' />
|
|
|
|
|
|
|
|
const element2 = <Element>
|
|
|
|
<span>hello</span>
|
|
|
|
<span>world</span>
|
|
|
|
</Element>
|
|
|
|
`)
|
2018-06-27 23:36:17 +03:00
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
2017-12-05 23:39:52 +03:00
|
|
|
|
|
|
|
expect(editor.isFoldableAtBufferRow(0)).toBe(true)
|
|
|
|
expect(editor.isFoldableAtBufferRow(1)).toBe(false)
|
|
|
|
expect(editor.isFoldableAtBufferRow(2)).toBe(false)
|
|
|
|
expect(editor.isFoldableAtBufferRow(3)).toBe(false)
|
|
|
|
expect(editor.isFoldableAtBufferRow(4)).toBe(true)
|
|
|
|
expect(editor.isFoldableAtBufferRow(5)).toBe(false)
|
|
|
|
|
|
|
|
editor.foldBufferRow(0)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
const element1 = <Element…/>
|
|
|
|
|
|
|
|
const element2 = <Element>
|
|
|
|
<span>hello</span>
|
|
|
|
<span>world</span>
|
|
|
|
</Element>
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.foldBufferRow(4)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
const element1 = <Element…/>
|
|
|
|
|
2017-12-08 02:29:11 +03:00
|
|
|
const element2 = <Element>…
|
|
|
|
</Element>
|
2017-12-05 23:39:52 +03:00
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
it('can fold entire nodes when no start or end parameters are specified', async () => {
|
2017-12-04 22:40:44 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
2017-12-05 23:39:52 +03:00
|
|
|
folds: [
|
2017-12-06 22:09:44 +03:00
|
|
|
// By default, for a node with no children, folds are started at the *end* of the first
|
|
|
|
// line of a node, and ended at the *beginning* of the last line.
|
2017-12-05 23:39:52 +03:00
|
|
|
{type: 'comment'}
|
|
|
|
]
|
2017-12-04 22:40:44 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
/**
|
|
|
|
* Important
|
|
|
|
*/
|
2017-12-06 22:09:44 +03:00
|
|
|
const x = 1 /*
|
|
|
|
Also important
|
|
|
|
*/
|
2017-12-04 22:40:44 +03:00
|
|
|
`)
|
2018-06-27 23:36:17 +03:00
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
2017-12-04 22:40:44 +03:00
|
|
|
|
|
|
|
expect(editor.isFoldableAtBufferRow(0)).toBe(true)
|
|
|
|
expect(editor.isFoldableAtBufferRow(1)).toBe(false)
|
|
|
|
expect(editor.isFoldableAtBufferRow(2)).toBe(false)
|
|
|
|
expect(editor.isFoldableAtBufferRow(3)).toBe(true)
|
|
|
|
expect(editor.isFoldableAtBufferRow(4)).toBe(false)
|
|
|
|
|
|
|
|
editor.foldBufferRow(0)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
/**… */
|
2017-12-06 22:09:44 +03:00
|
|
|
const x = 1 /*
|
|
|
|
Also important
|
|
|
|
*/
|
2017-12-04 22:40:44 +03:00
|
|
|
`)
|
|
|
|
|
|
|
|
editor.foldBufferRow(3)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
/**… */
|
2017-12-06 22:09:44 +03:00
|
|
|
const x = 1 /*…*/
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
it('tries each folding strategy for a given node in the order specified', async () => {
|
2017-12-06 22:09:44 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, cGrammarPath, {
|
|
|
|
parser: 'tree-sitter-c',
|
|
|
|
folds: [
|
|
|
|
// If the #ifdef has an `#else` clause, then end the fold there.
|
|
|
|
{
|
2017-12-08 02:29:11 +03:00
|
|
|
type: ['preproc_ifdef', 'preproc_elif'],
|
2017-12-06 22:09:44 +03:00
|
|
|
start: {index: 1},
|
2017-12-08 04:08:47 +03:00
|
|
|
end: {type: ['preproc_else', 'preproc_elif']}
|
2017-12-08 02:29:11 +03:00
|
|
|
},
|
2017-12-06 22:09:44 +03:00
|
|
|
|
|
|
|
// Otherwise, end the fold at the last child - the `#endif`.
|
|
|
|
{
|
|
|
|
type: 'preproc_ifdef',
|
|
|
|
start: {index: 1},
|
|
|
|
end: {index: -1}
|
|
|
|
},
|
|
|
|
|
|
|
|
// When folding an `#else` clause, the fold extends to the end of the clause.
|
|
|
|
{
|
|
|
|
type: 'preproc_else',
|
|
|
|
start: {index: 0}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
#ifndef FOO_H_
|
|
|
|
#define FOO_H_
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
|
|
#include <windows.h>
|
|
|
|
const char *path_separator = "\\";
|
|
|
|
|
2017-12-08 02:29:11 +03:00
|
|
|
#elif defined MACOS
|
|
|
|
|
|
|
|
#include <carbon.h>
|
|
|
|
const char *path_separator = "/";
|
|
|
|
|
2017-12-06 22:09:44 +03:00
|
|
|
#else
|
|
|
|
|
|
|
|
#include <dirent.h>
|
|
|
|
const char *path_separator = "/";
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#endif
|
2017-12-04 22:40:44 +03:00
|
|
|
`)
|
2018-06-27 23:36:17 +03:00
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
2017-12-06 22:09:44 +03:00
|
|
|
|
|
|
|
editor.foldBufferRow(3)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
#ifndef FOO_H_
|
|
|
|
#define FOO_H_
|
|
|
|
|
2017-12-08 02:29:11 +03:00
|
|
|
#ifdef _WIN32…
|
|
|
|
#elif defined MACOS
|
|
|
|
|
|
|
|
#include <carbon.h>
|
|
|
|
const char *path_separator = "/";
|
|
|
|
|
|
|
|
#else
|
2017-12-06 22:09:44 +03:00
|
|
|
|
|
|
|
#include <dirent.h>
|
|
|
|
const char *path_separator = "/";
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#endif
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.foldBufferRow(8)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
#ifndef FOO_H_
|
|
|
|
#define FOO_H_
|
|
|
|
|
2017-12-08 02:29:11 +03:00
|
|
|
#ifdef _WIN32…
|
|
|
|
#elif defined MACOS…
|
|
|
|
#else
|
|
|
|
|
|
|
|
#include <dirent.h>
|
|
|
|
const char *path_separator = "/";
|
2017-12-06 22:09:44 +03:00
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#endif
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.foldBufferRow(0)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
2017-12-08 02:29:11 +03:00
|
|
|
#ifndef FOO_H_…
|
|
|
|
#endif
|
2017-12-06 22:09:44 +03:00
|
|
|
`)
|
2017-12-08 02:30:48 +03:00
|
|
|
|
|
|
|
editor.foldAllAtIndentLevel(1)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
#ifndef FOO_H_
|
|
|
|
#define FOO_H_
|
|
|
|
|
|
|
|
#ifdef _WIN32…
|
|
|
|
#elif defined MACOS…
|
|
|
|
#else…
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#endif
|
|
|
|
`)
|
2017-12-06 22:09:44 +03:00
|
|
|
})
|
|
|
|
|
2018-06-19 00:27:49 +03:00
|
|
|
it('does not fold when the start and end parameters match the same child', async () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
|
|
|
|
parser: 'tree-sitter-html',
|
|
|
|
folds: [
|
|
|
|
{
|
|
|
|
type: 'element',
|
|
|
|
start: {index: 0},
|
|
|
|
end: {index: -1}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
<head>
|
|
|
|
<meta name='key-1', content='value-1'>
|
|
|
|
<meta name='key-2', content='value-2'>
|
|
|
|
</head>
|
|
|
|
`)
|
|
|
|
|
2018-06-27 23:36:17 +03:00
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
2018-06-19 00:27:49 +03:00
|
|
|
|
|
|
|
// Void elements have only one child
|
|
|
|
expect(editor.isFoldableAtBufferRow(1)).toBe(false)
|
|
|
|
expect(editor.isFoldableAtBufferRow(2)).toBe(false)
|
|
|
|
|
|
|
|
editor.foldBufferRow(0)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
<head>…
|
|
|
|
</head>
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2018-07-20 03:15:04 +03:00
|
|
|
it('can target named vs anonymous nodes as fold boundaries', async () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, rubyGrammarPath, {
|
|
|
|
parser: 'tree-sitter-ruby',
|
|
|
|
folds: [
|
|
|
|
{
|
|
|
|
type: 'elsif',
|
|
|
|
start: {index: 1},
|
|
|
|
|
|
|
|
// There are no double quotes around the `elsif` type. This indicates
|
|
|
|
// that we're targeting a *named* node in the syntax tree. The fold
|
|
|
|
// should end at the nested `elsif` node, not at the token that represents
|
|
|
|
// the literal string "elsif".
|
|
|
|
end: {type: ['else', 'elsif']}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'else',
|
|
|
|
|
|
|
|
// There are double quotes around the `else` type. This indicates that
|
|
|
|
// we're targetting an *anonymous* node in the syntax tree. The fold
|
|
|
|
// should start at the token representing the literal string "else",
|
|
|
|
// not at an `else` node.
|
|
|
|
start: {type: '"else"'}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
if a
|
|
|
|
b
|
|
|
|
elsif c
|
|
|
|
d
|
|
|
|
elsif e
|
|
|
|
f
|
|
|
|
else
|
|
|
|
g
|
|
|
|
end
|
|
|
|
`)
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
expect(languageMode.tree.rootNode.toString()).toBe(
|
|
|
|
"(program (if (identifier) " +
|
|
|
|
"(identifier) " +
|
|
|
|
"(elsif (identifier) " +
|
|
|
|
"(identifier) " +
|
|
|
|
"(elsif (identifier) " +
|
|
|
|
"(identifier) " +
|
|
|
|
"(else " +
|
|
|
|
"(identifier))))))"
|
|
|
|
)
|
|
|
|
|
|
|
|
editor.foldBufferRow(2)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
if a
|
|
|
|
b
|
|
|
|
elsif c…
|
|
|
|
elsif e
|
|
|
|
f
|
|
|
|
else
|
|
|
|
g
|
|
|
|
end
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.foldBufferRow(4)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
if a
|
|
|
|
b
|
|
|
|
elsif c…
|
|
|
|
elsif e…
|
|
|
|
else
|
|
|
|
g
|
|
|
|
end
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.foldBufferRow(6)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
if a
|
|
|
|
b
|
|
|
|
elsif c…
|
|
|
|
elsif e…
|
|
|
|
else…
|
|
|
|
end
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2017-12-06 22:09:44 +03:00
|
|
|
describe('when folding a node that ends with a line break', () => {
|
2018-06-22 03:06:55 +03:00
|
|
|
it('ends the fold at the end of the previous line', async () => {
|
2017-12-06 22:09:44 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, pythonGrammarPath, {
|
|
|
|
parser: 'tree-sitter-python',
|
|
|
|
folds: [
|
|
|
|
{
|
|
|
|
type: 'function_definition',
|
|
|
|
start: {type: ':'}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
def ab():
|
|
|
|
print 'a'
|
|
|
|
print 'b'
|
|
|
|
|
|
|
|
def cd():
|
|
|
|
print 'c'
|
|
|
|
print 'd'
|
|
|
|
`)
|
2018-06-27 23:36:17 +03:00
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
2017-12-06 22:09:44 +03:00
|
|
|
|
|
|
|
editor.foldBufferRow(0)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
def ab():…
|
|
|
|
|
|
|
|
def cd():
|
|
|
|
print 'c'
|
|
|
|
print 'd'
|
|
|
|
`)
|
|
|
|
})
|
2017-12-04 22:40:44 +03:00
|
|
|
})
|
2018-07-10 00:11:13 +03:00
|
|
|
|
|
|
|
it('folds code in injected languages', async () => {
|
|
|
|
const htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'html',
|
2018-07-10 00:11:13 +03:00
|
|
|
parser: 'tree-sitter-html',
|
|
|
|
scopes: {},
|
|
|
|
folds: [{
|
|
|
|
type: ['element', 'raw_element'],
|
|
|
|
start: {index: 0},
|
|
|
|
end: {index: -1}
|
|
|
|
}],
|
|
|
|
injectionRegExp: 'html'
|
|
|
|
})
|
|
|
|
|
|
|
|
const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'javascript',
|
2018-07-10 00:11:13 +03:00
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {},
|
|
|
|
folds: [{
|
|
|
|
type: ['template_string'],
|
|
|
|
start: {index: 0},
|
|
|
|
end: {index: -1},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
start: {index: 0, type: '('},
|
|
|
|
end: {index: -1, type: ')'}
|
|
|
|
}],
|
|
|
|
injectionRegExp: 'javascript',
|
|
|
|
injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
|
|
|
|
|
|
|
buffer.setText(
|
|
|
|
`a = html \`
|
|
|
|
<div>
|
|
|
|
c\${def(
|
|
|
|
1,
|
|
|
|
2,
|
|
|
|
3,
|
|
|
|
)}e\${f}g
|
|
|
|
</div>
|
|
|
|
\`
|
|
|
|
`
|
|
|
|
)
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar: jsGrammar, grammars: atom.grammars})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
editor.foldBufferRow(2)
|
|
|
|
expect(getDisplayText(editor)).toBe(
|
|
|
|
`a = html \`
|
|
|
|
<div>
|
2018-07-20 20:43:05 +03:00
|
|
|
c\${def(…
|
|
|
|
)}e\${f}g
|
2018-07-10 00:11:13 +03:00
|
|
|
</div>
|
|
|
|
\`
|
|
|
|
`
|
|
|
|
)
|
|
|
|
|
|
|
|
editor.foldBufferRow(1)
|
|
|
|
expect(getDisplayText(editor)).toBe(
|
|
|
|
`a = html \`
|
|
|
|
<div>…
|
|
|
|
</div>
|
|
|
|
\`
|
|
|
|
`
|
|
|
|
)
|
|
|
|
|
|
|
|
editor.foldBufferRow(0)
|
|
|
|
expect(getDisplayText(editor)).toBe(
|
|
|
|
`a = html \`…\`
|
|
|
|
`
|
|
|
|
)
|
|
|
|
})
|
2017-12-04 22:02:24 +03:00
|
|
|
})
|
2017-12-04 23:07:05 +03:00
|
|
|
|
2017-12-16 04:10:20 +03:00
|
|
|
describe('.scopeDescriptorForPosition', () => {
|
2018-06-22 03:06:55 +03:00
|
|
|
it('returns a scope descriptor representing the given position in the syntax tree', async () => {
|
2017-12-16 04:10:20 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-20 20:01:13 +03:00
|
|
|
scopeName: 'source.js',
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
program: 'source.js',
|
|
|
|
property_identifier: 'property.name'
|
|
|
|
}
|
2017-12-16 04:10:20 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText('foo({bar: baz});')
|
2018-06-27 23:36:17 +03:00
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
2018-08-20 20:01:13 +03:00
|
|
|
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'
|
2017-12-16 04:10:20 +03:00
|
|
|
])
|
|
|
|
})
|
2018-07-16 23:43:10 +03:00
|
|
|
|
|
|
|
it('includes nodes in injected syntax trees', async () => {
|
|
|
|
const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-20 20:01:13 +03:00
|
|
|
scopeName: 'source.js',
|
2018-07-16 23:43:10 +03:00
|
|
|
parser: 'tree-sitter-javascript',
|
2018-08-20 20:01:13 +03:00
|
|
|
scopes: {
|
|
|
|
program: 'source.js',
|
|
|
|
template_string: 'string.quoted',
|
|
|
|
interpolation: 'meta.embedded',
|
|
|
|
property_identifier: 'property.name'
|
|
|
|
},
|
2018-07-16 23:43:10 +03:00
|
|
|
injectionRegExp: 'javascript',
|
|
|
|
injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
const htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
|
2018-08-20 20:01:13 +03:00
|
|
|
scopeName: 'text.html',
|
2018-07-16 23:43:10 +03:00
|
|
|
parser: 'tree-sitter-html',
|
2018-08-20 20:01:13 +03:00
|
|
|
scopes: {
|
|
|
|
fragment: 'text.html',
|
|
|
|
raw_element: 'script.tag'
|
|
|
|
},
|
2018-07-16 23:43:10 +03:00
|
|
|
injectionRegExp: 'html',
|
|
|
|
injectionPoints: [SCRIPT_TAG_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
atom.grammars.addGrammar(jsGrammar)
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
|
|
|
|
|
|
|
buffer.setText(`
|
|
|
|
<div>
|
|
|
|
<script>
|
|
|
|
html \`
|
|
|
|
<span>\${person.name}</span>
|
|
|
|
\`
|
|
|
|
</script>
|
|
|
|
</div>
|
|
|
|
`)
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar: htmlGrammar, grammars: atom.grammars})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
const position = buffer.findSync('name').start
|
|
|
|
expect(languageMode.scopeDescriptorForPosition(position).getScopesArray()).toEqual([
|
2018-08-20 20:01:13 +03:00
|
|
|
'text.html',
|
|
|
|
'script.tag',
|
|
|
|
'source.js',
|
|
|
|
'string.quoted',
|
|
|
|
'text.html',
|
|
|
|
'property.name'
|
2018-07-16 23:43:10 +03:00
|
|
|
])
|
|
|
|
})
|
2018-08-28 03:03:20 +03:00
|
|
|
|
|
|
|
it('includes the root scope name even when the given position is in trailing whitespace at EOF', () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
scopeName: 'source.js',
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
program: 'source.js',
|
|
|
|
property_identifier: 'property.name'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText('a; ')
|
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
expect(editor.scopeDescriptorForBufferPosition([0, 3]).getScopesArray()).toEqual([
|
|
|
|
'source.js'
|
|
|
|
])
|
|
|
|
})
|
2017-12-16 04:10:20 +03:00
|
|
|
})
|
|
|
|
|
2018-07-21 04:22:12 +03:00
|
|
|
describe('.bufferRangeForScopeAtPosition(selector?, position)', () => {
|
|
|
|
describe('when selector = null', () => {
|
|
|
|
it('returns the range of the smallest node at position', async () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'javascript',
|
2018-07-21 04:22:12 +03:00
|
|
|
parser: 'tree-sitter-javascript'
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText('foo({bar: baz});')
|
|
|
|
|
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
expect(editor.bufferRangeForScopeAtPosition(null, [0, 6])).toEqual(
|
|
|
|
[[0, 5], [0, 8]]
|
|
|
|
)
|
|
|
|
expect(editor.bufferRangeForScopeAtPosition(null, [0, 9])).toEqual(
|
|
|
|
[[0, 8], [0, 9]]
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('includes nodes in injected syntax trees', async () => {
|
|
|
|
const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'javascript',
|
2018-07-21 04:22:12 +03:00
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {},
|
|
|
|
injectionRegExp: 'javascript',
|
|
|
|
injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
const htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'html',
|
2018-07-21 04:22:12 +03:00
|
|
|
parser: 'tree-sitter-html',
|
|
|
|
scopes: {},
|
|
|
|
injectionRegExp: 'html',
|
|
|
|
injectionPoints: [SCRIPT_TAG_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
atom.grammars.addGrammar(jsGrammar)
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
|
|
|
|
|
|
|
buffer.setText(`
|
|
|
|
<div>
|
|
|
|
<script>
|
|
|
|
html \`
|
|
|
|
<span>\${person.name}</span>
|
|
|
|
\`
|
|
|
|
</script>
|
|
|
|
</div>
|
|
|
|
`)
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar: htmlGrammar, grammars: atom.grammars})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
const nameProperty = buffer.findSync('name')
|
|
|
|
const {start} = nameProperty
|
|
|
|
const position = Object.assign({}, start, {column: start.column + 2})
|
|
|
|
expect(languageMode.bufferRangeForScopeAtPosition(null, position))
|
|
|
|
.toEqual(nameProperty)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('with a selector', () => {
|
|
|
|
it('returns the range of the smallest matching node at position', async () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'javascript',
|
2018-07-21 04:22:12 +03:00
|
|
|
parser: 'tree-sitter-javascript'
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText('foo({bar: baz});')
|
|
|
|
|
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
expect(editor.bufferRangeForScopeAtPosition('.property_identifier', [0, 6])).toEqual(
|
|
|
|
buffer.findSync('bar')
|
|
|
|
)
|
|
|
|
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}')
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('includes nodes in injected syntax trees', async () => {
|
|
|
|
const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'javascript',
|
2018-07-21 04:22:12 +03:00
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {},
|
|
|
|
injectionRegExp: 'javascript',
|
|
|
|
injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
const htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'html',
|
2018-07-21 04:22:12 +03:00
|
|
|
parser: 'tree-sitter-html',
|
|
|
|
scopes: {},
|
|
|
|
injectionRegExp: 'html',
|
|
|
|
injectionPoints: [SCRIPT_TAG_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
atom.grammars.addGrammar(jsGrammar)
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
|
|
|
|
|
|
|
buffer.setText(`
|
|
|
|
<div>
|
|
|
|
<script>
|
|
|
|
html \`
|
|
|
|
<span>\${person.name}</span>
|
|
|
|
\`
|
|
|
|
</script>
|
|
|
|
</div>
|
|
|
|
`)
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar: htmlGrammar, grammars: atom.grammars})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
const nameProperty = buffer.findSync('name')
|
|
|
|
const {start} = nameProperty
|
|
|
|
const position = Object.assign({}, start, {column: start.column + 2})
|
|
|
|
expect(languageMode.bufferRangeForScopeAtPosition('.property_identifier', position))
|
|
|
|
.toEqual(nameProperty)
|
|
|
|
expect(languageMode.bufferRangeForScopeAtPosition('.element', position))
|
|
|
|
.toEqual(buffer.findSync('<span>\\${person\\.name}</span>'))
|
|
|
|
})
|
|
|
|
|
|
|
|
it('accepts node-matching functions as selectors', async () => {
|
|
|
|
const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'javascript',
|
2018-07-21 04:22:12 +03:00
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {},
|
|
|
|
injectionRegExp: 'javascript',
|
|
|
|
injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
const htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'html',
|
2018-07-21 04:22:12 +03:00
|
|
|
parser: 'tree-sitter-html',
|
|
|
|
scopes: {},
|
|
|
|
injectionRegExp: 'html',
|
|
|
|
injectionPoints: [SCRIPT_TAG_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
atom.grammars.addGrammar(jsGrammar)
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
|
|
|
|
|
|
|
buffer.setText(`
|
|
|
|
<div>
|
|
|
|
<script>
|
|
|
|
html \`
|
|
|
|
<span>\${person.name}</span>
|
|
|
|
\`
|
|
|
|
</script>
|
|
|
|
</div>
|
|
|
|
`)
|
|
|
|
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar: htmlGrammar, grammars: atom.grammars})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
const nameProperty = buffer.findSync('name')
|
|
|
|
const {start} = nameProperty
|
|
|
|
const position = Object.assign({}, start, {column: start.column + 2})
|
|
|
|
const templateStringInCallExpression = node =>
|
|
|
|
node.type === 'template_string' && node.parent.type === 'call_expression'
|
|
|
|
expect(languageMode.bufferRangeForScopeAtPosition(templateStringInCallExpression, position))
|
|
|
|
.toEqual([[3, 19], [5, 15]])
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('.getSyntaxNodeAtPosition(position, where?)', () => {
|
2018-07-23 17:36:25 +03:00
|
|
|
it('returns the range of the smallest matching node at position', async () => {
|
2018-07-21 04:22:12 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'javascript',
|
2018-07-21 04:22:12 +03:00
|
|
|
parser: 'tree-sitter-javascript'
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText('foo(bar({x: 2}));')
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
expect(languageMode.getSyntaxNodeAtPosition([0, 6]).range).toEqual(
|
|
|
|
buffer.findSync('bar')
|
|
|
|
)
|
|
|
|
const findFoo = node =>
|
|
|
|
node.type === 'call_expression' && node.firstChild.text === 'foo'
|
|
|
|
expect(languageMode.getSyntaxNodeAtPosition([0, 6], findFoo).range).toEqual(
|
|
|
|
[[0, 0], [0, buffer.getText().length - 1]]
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2018-07-10 00:11:13 +03:00
|
|
|
describe('TextEditor.selectLargerSyntaxNode and .selectSmallerSyntaxNode', () => {
|
2018-07-09 20:43:17 +03:00
|
|
|
it('expands and contracts the selection based on the syntax tree', async () => {
|
2017-12-04 23:07:05 +03:00
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {'program': 'source'}
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
function a (b, c, d) {
|
|
|
|
eee.f()
|
|
|
|
g()
|
|
|
|
}
|
|
|
|
`)
|
2018-06-27 23:36:17 +03:00
|
|
|
|
2018-05-16 03:53:47 +03:00
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
2017-12-04 23:07:05 +03:00
|
|
|
|
|
|
|
editor.setCursorBufferPosition([1, 3])
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('eee')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('eee.f')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('eee.f()')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('{\n eee.f()\n g()\n}')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('function a (b, c, d) {\n eee.f()\n g()\n}')
|
|
|
|
|
|
|
|
editor.selectSmallerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('{\n eee.f()\n g()\n}')
|
|
|
|
editor.selectSmallerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('eee.f()')
|
|
|
|
editor.selectSmallerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('eee.f')
|
|
|
|
editor.selectSmallerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('eee')
|
|
|
|
editor.selectSmallerSyntaxNode()
|
|
|
|
expect(editor.getSelectedBufferRange()).toEqual([[1, 3], [1, 3]])
|
|
|
|
})
|
2018-07-09 20:43:17 +03:00
|
|
|
|
|
|
|
it('handles injected languages', async () => {
|
|
|
|
const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'javascript',
|
2018-07-09 20:43:17 +03:00
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {
|
|
|
|
'property_identifier': 'property',
|
|
|
|
'call_expression > identifier': 'function',
|
|
|
|
'template_string': 'string',
|
|
|
|
'template_substitution > "${"': 'interpolation',
|
|
|
|
'template_substitution > "}"': 'interpolation'
|
|
|
|
},
|
|
|
|
injectionRegExp: 'javascript',
|
|
|
|
injectionPoints: [HTML_TEMPLATE_LITERAL_INJECTION_POINT]
|
|
|
|
})
|
|
|
|
|
|
|
|
const htmlGrammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, {
|
2018-08-18 20:35:41 +03:00
|
|
|
scopeName: 'html',
|
2018-07-09 20:43:17 +03:00
|
|
|
parser: 'tree-sitter-html',
|
|
|
|
scopes: {
|
|
|
|
fragment: 'html',
|
|
|
|
tag_name: 'tag',
|
|
|
|
attribute_name: 'attr'
|
|
|
|
},
|
|
|
|
injectionRegExp: 'html'
|
|
|
|
})
|
|
|
|
|
|
|
|
atom.grammars.addGrammar(htmlGrammar)
|
|
|
|
|
|
|
|
buffer.setText('a = html ` <b>c${def()}e${f}g</b> `')
|
|
|
|
const languageMode = new TreeSitterLanguageMode({buffer, grammar: jsGrammar, grammars: atom.grammars})
|
|
|
|
buffer.setLanguageMode(languageMode)
|
|
|
|
|
|
|
|
editor.setCursorBufferPosition({row: 0, column: buffer.getText().indexOf('ef()')})
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('def')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('def()')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('${def()}')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('c${def()}e${f}g')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('<b>c${def()}e${f}g</b>')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe(' <b>c${def()}e${f}g</b> ')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('` <b>c${def()}e${f}g</b> `')
|
|
|
|
editor.selectLargerSyntaxNode()
|
|
|
|
expect(editor.getSelectedText()).toBe('html ` <b>c${def()}e${f}g</b> `')
|
|
|
|
})
|
2017-12-04 23:07:05 +03:00
|
|
|
})
|
2017-12-01 04:13:30 +03:00
|
|
|
})
|
|
|
|
|
2018-07-06 23:53:13 +03:00
|
|
|
function nextHighlightingUpdate (languageMode) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const subscription = languageMode.onDidChangeHighlighting(() => {
|
|
|
|
subscription.dispose()
|
|
|
|
resolve()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-12-04 22:02:24 +03:00
|
|
|
function getDisplayText (editor) {
|
|
|
|
return editor.displayLayer.getText()
|
|
|
|
}
|
|
|
|
|
2017-12-15 20:44:45 +03:00
|
|
|
function expectTokensToEqual (editor, expectedTokenLines) {
|
|
|
|
const lastRow = editor.getLastScreenRow()
|
|
|
|
|
|
|
|
// Assert that the correct tokens are returned regardless of which row
|
|
|
|
// the highlighting iterator starts on.
|
|
|
|
for (let startRow = 0; startRow <= lastRow; startRow++) {
|
2018-01-26 01:02:31 +03:00
|
|
|
|
|
|
|
// Clear the screen line cache between iterations, but not on the first
|
|
|
|
// iteration, so that the first iteration tests that the cache has been
|
|
|
|
// correctly invalidated by any changes.
|
|
|
|
if (startRow > 0) {
|
|
|
|
editor.displayLayer.clearSpatialIndex()
|
|
|
|
}
|
|
|
|
|
2017-12-15 20:44:45 +03:00
|
|
|
editor.displayLayer.getScreenLines(startRow, Infinity)
|
|
|
|
|
|
|
|
const tokenLines = []
|
|
|
|
for (let row = startRow; row <= lastRow; row++) {
|
|
|
|
tokenLines[row] = editor.tokensForScreenRow(row).map(({text, scopes}) => ({
|
2017-12-01 04:13:30 +03:00
|
|
|
text,
|
|
|
|
scopes: scopes.map(scope => scope
|
|
|
|
.split(' ')
|
2018-07-16 20:38:37 +03:00
|
|
|
.map(className => className.replace('syntax--', ''))
|
2017-12-01 04:13:30 +03:00
|
|
|
.join(' '))
|
|
|
|
}))
|
2017-12-15 20:44:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for (let row = startRow; row <= lastRow; row++) {
|
|
|
|
const tokenLine = tokenLines[row]
|
|
|
|
const expectedTokenLine = expectedTokenLines[row]
|
2017-12-04 21:18:38 +03:00
|
|
|
|
2017-12-15 20:44:45 +03:00
|
|
|
expect(tokenLine.length).toEqual(expectedTokenLine.length)
|
|
|
|
for (let i = 0; i < tokenLine.length; i++) {
|
|
|
|
expect(tokenLine[i]).toEqual(expectedTokenLine[i], `Token ${i}, startRow: ${startRow}`)
|
|
|
|
}
|
|
|
|
}
|
2017-12-04 21:18:38 +03:00
|
|
|
}
|
2018-01-26 01:02:31 +03:00
|
|
|
|
|
|
|
// Fully populate the screen line cache again so that cache invalidation
|
|
|
|
// due to subsequent edits can be tested.
|
|
|
|
editor.displayLayer.getScreenLines(0, Infinity)
|
2017-12-01 04:13:30 +03:00
|
|
|
}
|
2018-07-09 20:43:17 +03:00
|
|
|
|
|
|
|
const HTML_TEMPLATE_LITERAL_INJECTION_POINT = {
|
|
|
|
type: 'call_expression',
|
|
|
|
language (node) {
|
|
|
|
if (node.lastChild.type === 'template_string' && node.firstChild.type === 'identifier') {
|
|
|
|
return node.firstChild.text
|
|
|
|
}
|
|
|
|
},
|
|
|
|
content (node) {
|
|
|
|
return node.lastChild
|
|
|
|
}
|
|
|
|
}
|
2018-07-16 23:43:10 +03:00
|
|
|
|
|
|
|
const SCRIPT_TAG_INJECTION_POINT = {
|
|
|
|
type: 'raw_element',
|
|
|
|
language () { return 'javascript' },
|
|
|
|
content (node) { return node.child(1) }
|
|
|
|
}
|