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')
|
|
|
|
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')
|
|
|
|
|
|
|
|
describe('TreeSitterLanguageMode', () => {
|
|
|
|
let editor, buffer
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
editor = await atom.workspace.open('')
|
|
|
|
buffer = editor.getBuffer()
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('highlighting', () => {
|
2017-12-04 21:18:38 +03:00
|
|
|
it('applies the most specific scope mapping to each node in the syntax tree', () => {
|
|
|
|
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'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
buffer.setText('aa.bbb = cc(d.eee());')
|
2017-12-04 21:18:38 +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-04 21:18:38 +03:00
|
|
|
])
|
|
|
|
})
|
|
|
|
|
|
|
|
it('can start or end multiple scopes at the same position', () => {
|
|
|
|
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',
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
buffer.setText('a = bb.ccc();')
|
|
|
|
expectTokensToEqual(editor, [
|
|
|
|
{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-01 04:13:30 +03:00
|
|
|
})
|
|
|
|
})
|
2017-12-04 22:02:24 +03:00
|
|
|
|
|
|
|
describe('folding', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
editor.displayLayer.reset({foldCharacter: '…'})
|
|
|
|
})
|
|
|
|
|
2017-12-06 22:09:44 +03:00
|
|
|
it('can fold nodes that start and end with specified tokens', () => {
|
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.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.screenLineForScreenRow(0)
|
|
|
|
|
|
|
|
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 {
|
|
|
|
getB (…) {
|
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 {
|
|
|
|
getB (…) {…}
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
2017-12-04 22:40:44 +03:00
|
|
|
|
2017-12-06 22:09:44 +03:00
|
|
|
it('can fold nodes of specified types', () => {
|
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.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
buffer.setText(dedent `
|
|
|
|
const element1 = <Element
|
|
|
|
className='submit'
|
|
|
|
id='something' />
|
|
|
|
|
|
|
|
const element2 = <Element>
|
|
|
|
<span>hello</span>
|
|
|
|
<span>world</span>
|
|
|
|
</Element>
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.screenLineForScreenRow(0)
|
|
|
|
|
|
|
|
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
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2017-12-06 22:09:44 +03:00
|
|
|
it('can fold entire nodes when no start or end parameters are specified', () => {
|
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.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
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
|
|
|
`)
|
|
|
|
|
|
|
|
editor.screenLineForScreenRow(0)
|
|
|
|
|
|
|
|
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 /*…*/
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('tries each folding strategy for a given node in the order specified', () => {
|
|
|
|
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},
|
|
|
|
end: {type: 'preproc_else'}
|
|
|
|
},
|
2017-12-08 02:29:11 +03:00
|
|
|
{
|
|
|
|
type: ['preproc_ifdef', 'preproc_elif'],
|
|
|
|
start: {index: 1},
|
|
|
|
end: {type: 'preproc_elif'}
|
|
|
|
},
|
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.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
|
|
|
|
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
|
|
|
`)
|
2017-12-06 22:09:44 +03:00
|
|
|
|
|
|
|
editor.screenLineForScreenRow(0)
|
|
|
|
|
|
|
|
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
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('when folding a node that ends with a line break', () => {
|
|
|
|
it('ends the fold at the end of the previous line', () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, pythonGrammarPath, {
|
|
|
|
parser: 'tree-sitter-python',
|
|
|
|
folds: [
|
|
|
|
{
|
|
|
|
type: 'function_definition',
|
|
|
|
start: {type: ':'}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
|
|
|
|
buffer.setText(dedent `
|
|
|
|
def ab():
|
|
|
|
print 'a'
|
|
|
|
print 'b'
|
|
|
|
|
|
|
|
def cd():
|
|
|
|
print 'c'
|
|
|
|
print 'd'
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.screenLineForScreenRow(0)
|
|
|
|
|
|
|
|
editor.foldBufferRow(0)
|
|
|
|
expect(getDisplayText(editor)).toBe(dedent `
|
|
|
|
def ab():…
|
|
|
|
|
|
|
|
def cd():
|
|
|
|
print 'c'
|
|
|
|
print 'd'
|
|
|
|
`)
|
|
|
|
})
|
2017-12-04 22:40:44 +03:00
|
|
|
})
|
2017-12-04 22:02:24 +03:00
|
|
|
})
|
2017-12-04 23:07:05 +03:00
|
|
|
|
|
|
|
describe('TextEditor.selectLargerSyntaxNode and .selectSmallerSyntaxNode', () => {
|
|
|
|
it('expands and contract the selection based on the syntax tree', () => {
|
|
|
|
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
|
|
|
parser: 'tree-sitter-javascript',
|
|
|
|
scopes: {'program': 'source'}
|
|
|
|
})
|
|
|
|
|
|
|
|
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
|
|
|
buffer.setText(dedent `
|
|
|
|
function a (b, c, d) {
|
|
|
|
eee.f()
|
|
|
|
g()
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
|
|
|
|
editor.screenLineForScreenRow(0)
|
|
|
|
|
|
|
|
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]])
|
|
|
|
})
|
|
|
|
})
|
2017-12-01 04:13:30 +03:00
|
|
|
})
|
|
|
|
|
2017-12-04 22:02:24 +03:00
|
|
|
function getDisplayText (editor) {
|
|
|
|
return editor.displayLayer.getText()
|
|
|
|
}
|
|
|
|
|
2017-12-04 21:18:38 +03:00
|
|
|
function expectTokensToEqual (editor, expectedTokens) {
|
|
|
|
const tokens = []
|
2017-12-01 04:13:30 +03:00
|
|
|
for (let row = 0, lastRow = editor.getLastScreenRow(); row <= lastRow; row++) {
|
2017-12-04 21:18:38 +03:00
|
|
|
tokens.push(
|
|
|
|
...editor.tokensForScreenRow(row).map(({text, scopes}) => ({
|
2017-12-01 04:13:30 +03:00
|
|
|
text,
|
|
|
|
scopes: scopes.map(scope => scope
|
|
|
|
.split(' ')
|
|
|
|
.map(className => className.slice('syntax--'.length))
|
|
|
|
.join(' '))
|
|
|
|
}))
|
|
|
|
)
|
|
|
|
}
|
2017-12-04 21:18:38 +03:00
|
|
|
|
|
|
|
expect(tokens.length).toEqual(expectedTokens.length)
|
|
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
|
|
expect(tokens[i]).toEqual(expectedTokens[i], `Token ${i}`)
|
|
|
|
}
|
2017-12-01 04:13:30 +03:00
|
|
|
}
|