diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson index 7161a8478..d5cc7b7da 100644 --- a/keymaps/darwin.cson +++ b/keymaps/darwin.cson @@ -161,6 +161,8 @@ 'ctrl-alt-shift-right': 'editor:select-to-next-subword-boundary' 'ctrl-alt-backspace': 'editor:delete-to-beginning-of-subword' 'ctrl-alt-delete': 'editor:delete-to-end-of-subword' + 'ctrl-alt-up': 'editor:select-larger-syntax-node' + 'ctrl-alt-down': 'editor:select-smaller-syntax-node' 'atom-workspace atom-text-editor:not([mini])': # Atom specific diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 0bacfbb8e..a367e6188 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -160,6 +160,8 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage 'editor:select-to-previous-subword-boundary': -> @selectToPreviousSubwordBoundary() 'editor:select-to-first-character-of-line': -> @selectToFirstCharacterOfLine() 'editor:select-line': -> @selectLinesContainingCursors() + 'editor:select-larger-syntax-node': -> @selectLargerSyntaxNode() + 'editor:select-smaller-syntax-node': -> @selectSmallerSyntaxNode() }), false ) diff --git a/src/text-editor.js b/src/text-editor.js index 016d076b0..b3d0e592a 100644 --- a/src/text-editor.js +++ b/src/text-editor.js @@ -3053,13 +3053,33 @@ class TextEditor { return this.expandSelectionsBackward(selection => selection.selectToBeginningOfPreviousParagraph()) } + // Extended: For each selection, select the syntax node that contains + // that selection. selectLargerSyntaxNode () { const languageMode = this.buffer.getLanguageMode() if (!languageMode.getRangeForSyntaxNodeContainingRange) return this.expandSelectionsForward(selection => { - const range = languageMode.getRangeForSyntaxNodeContainingRange(selection.getBufferRange()) - if (range) selection.setBufferRange(range) + const currentRange = selection.getBufferRange() + const newRange = languageMode.getRangeForSyntaxNodeContainingRange(currentRange) + if (newRange) { + if (!selection._rangeStack) selection._rangeStack = [] + selection._rangeStack.push(currentRange) + selection.setBufferRange(newRange) + } + }) + } + + // Extended: Undo the effect a preceding call to {::selectLargerSyntaxNode}. + selectSmallerSyntaxNode () { + this.expandSelectionsForward(selection => { + if (selection._rangeStack) { + const lastRange = selection._rangeStack[selection._rangeStack.length - 1] + if (lastRange && selection.getBufferRange().containsRange(lastRange)) { + selection._rangeStack.length-- + selection.setBufferRange(lastRange) + } + } }) } diff --git a/src/tree-sitter-language-mode.js b/src/tree-sitter-language-mode.js index 0d2e36af6..aa2c50a18 100644 --- a/src/tree-sitter-language-mode.js +++ b/src/tree-sitter-language-mode.js @@ -220,6 +220,20 @@ class TreeSitterLanguageMode { } } + /* + * Syntax Tree APIs + */ + + getRangeForSyntaxNodeContainingRange (range) { + const startIndex = this.buffer.characterIndexForPosition(range.start) + const endIndex = this.buffer.characterIndexForPosition(range.end) + let node = this.document.rootNode.descendantForIndex(startIndex, endIndex - 1) + while (node && node.startIndex === startIndex && node.endIndex === endIndex) { + node = node.parent + } + if (node) return new Range(node.startPosition, node.endPosition) + } + /* * Section - Backward compatibility shims */