From 3f775b550510ec2483c3fc8220dd5acf54f87449 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 6 Dec 2017 11:09:44 -0800 Subject: [PATCH] Fix folding of internal nodes when fold end isn't specified --- spec/tree-sitter-language-mode-spec.js | 157 ++++++++++++++++++++++--- src/tree-sitter-language-mode.js | 44 ++++--- 2 files changed, 163 insertions(+), 38 deletions(-) diff --git a/spec/tree-sitter-language-mode-spec.js b/spec/tree-sitter-language-mode-spec.js index 1cc9afc94..426291e5f 100644 --- a/spec/tree-sitter-language-mode-spec.js +++ b/spec/tree-sitter-language-mode-spec.js @@ -6,6 +6,8 @@ const TextEditor = require('../src/text-editor') const TreeSitterGrammar = require('../src/tree-sitter-grammar') const TreeSitterLanguageMode = require('../src/tree-sitter-language-mode') +const cGrammarPath = require.resolve('language-c/grammars/tree-sitter-c.cson') +const pythonGrammarPath = require.resolve('language-python/grammars/tree-sitter-python.cson') const jsGrammarPath = require.resolve('language-javascript/grammars/tree-sitter-javascript.cson') describe('TreeSitterLanguageMode', () => { @@ -73,7 +75,7 @@ describe('TreeSitterLanguageMode', () => { editor.displayLayer.reset({foldCharacter: '…'}) }) - it('can fold nodes that start and end with specified tokens and span multiple lines', () => { + it('can fold nodes that start and end with specified tokens', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', folds: [ @@ -107,6 +109,7 @@ describe('TreeSitterLanguageMode', () => { expect(editor.isFoldableAtBufferRow(2)).toBe(true) expect(editor.isFoldableAtBufferRow(3)).toBe(false) expect(editor.isFoldableAtBufferRow(4)).toBe(true) + expect(editor.isFoldableAtBufferRow(5)).toBe(false) editor.foldBufferRow(2) expect(getDisplayText(editor)).toBe(dedent ` @@ -127,20 +130,24 @@ describe('TreeSitterLanguageMode', () => { `) }) - it('can fold nodes that start and end with specified tokens and span multiple lines', () => { + it('can fold nodes of specified types', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', folds: [ + // Start the fold after the first child (the opening tag) and end it at the last child + // (the closing tag). { type: 'jsx_element', - start: {index: 0, type: 'jsx_opening_element'}, - end: {index: -1, type: 'jsx_closing_element'} + start: {index: 0}, + end: {index: -1} }, + + // End the fold at the *second* to last child of the self-closing tag: the `/`. { type: 'jsx_self_closing_element', start: {index: 1}, - end: {type: '/', index: -2} - }, + end: {index: -2} + } ] }) @@ -183,11 +190,12 @@ describe('TreeSitterLanguageMode', () => { `) }) - it('can fold specified types of multi-line nodes', () => { + it('can fold entire nodes when no start or end parameters are specified', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', folds: [ - {type: 'template_string'}, + // 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. {type: 'comment'} ] }) @@ -197,9 +205,9 @@ describe('TreeSitterLanguageMode', () => { /** * Important */ - const x = \`one - two - three\` + const x = 1 /* + Also important + */ `) editor.screenLineForScreenRow(0) @@ -213,17 +221,136 @@ describe('TreeSitterLanguageMode', () => { editor.foldBufferRow(0) expect(getDisplayText(editor)).toBe(dedent ` /**… */ - const x = \`one - two - three\` + const x = 1 /* + Also important + */ `) editor.foldBufferRow(3) expect(getDisplayText(editor)).toBe(dedent ` /**… */ - const x = \`one… three\` + 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. + { + type: 'preproc_ifdef', + start: {index: 1}, + end: {type: 'preproc_else'} + }, + + // 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 + const char *path_separator = "\\"; + + #else + + #include + const char *path_separator = "/"; + + #endif + + #endif + `) + + editor.screenLineForScreenRow(0) + + editor.foldBufferRow(3) + expect(getDisplayText(editor)).toBe(dedent ` + #ifndef FOO_H_ + #define FOO_H_ + + #ifdef _WIN32…#else + + #include + const char *path_separator = "/"; + + #endif + + #endif + `) + + editor.foldBufferRow(8) + expect(getDisplayText(editor)).toBe(dedent ` + #ifndef FOO_H_ + #define FOO_H_ + + #ifdef _WIN32…#else… + + #endif + + #endif + `) + + editor.foldBufferRow(0) + expect(getDisplayText(editor)).toBe(dedent ` + #ifndef FOO_H_…#endif + `) + }) + + 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' + `) + }) + }) }) describe('TextEditor.selectLargerSyntaxNode and .selectSmallerSyntaxNode', () => { diff --git a/src/tree-sitter-language-mode.js b/src/tree-sitter-language-mode.js index 166816d0d..f47d89db7 100644 --- a/src/tree-sitter-language-mode.js +++ b/src/tree-sitter-language-mode.js @@ -203,57 +203,55 @@ class TreeSitterLanguageMode { } } - let childBeforeFold + let foldStart const startEntry = foldEntry.start if (startEntry) { if (startEntry.index != null) { - childBeforeFold = children[startEntry.index] - if (!childBeforeFold) continue - if (startEntry.type && startEntry.type !== childBeforeFold.type) continue + const child = children[startEntry.index] + if (!child || (startEntry.type && startEntry.type !== child.type)) continue + foldStart = child.endPosition } else { if (!childTypes) childTypes = children.map(child => child.type) - let index = childTypes.indexOf(startEntry.type) + const index = childTypes.indexOf(startEntry.type) if (index === -1) continue - childBeforeFold = children[index] + foldStart = children[index].endPosition } } - let childAfterFold + let foldEnd const endEntry = foldEntry.end if (endEntry) { if (endEntry.index != null) { const index = endEntry.index < 0 ? childCount + endEntry.index : endEntry.index - childAfterFold = children[index] - if (!childAfterFold) continue - if (endEntry.type && endEntry.type !== childAfterFold.type) continue + const child = children[index] + if (!child || (endEntry.type && endEntry.type !== child.type)) continue + foldEnd = child.startPosition } else { if (!childTypes) childTypes = children.map(child => child.type) - let index = childTypes.lastIndexOf(endEntry.type) + const index = childTypes.lastIndexOf(endEntry.type) if (index === -1) continue - childAfterFold = children[index] + foldEnd = children[index].startPosition } } if (existenceOnly) return true - let start, end - if (childBeforeFold) { - start = childBeforeFold.endPosition - } else { - start = new Point(node.startPosition.row, Infinity) + if (!foldStart) { + foldStart = new Point(node.startPosition.row, Infinity) } - if (childAfterFold) { - end = childAfterFold.startPosition - } else { + + if (!foldEnd) { const {endPosition} = node if (endPosition.column === 0) { - end = Point(endPosition.row - 1, Infinity) + foldEnd = Point(endPosition.row - 1, Infinity) + } else if (childCount > 0) { + foldEnd = endPosition } else { - end = Point(endPosition.row, 0) + foldEnd = Point(endPosition.row, 0) } } - return new Range(start, end) + return new Range(foldStart, foldEnd) } }