diff --git a/README.md b/README.md index 70c8504b..06257b9a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ to see examples of community contributions. We're looking forward to yours, too. ### Documentation * [Overview](https://github.com/atom/vim-mode/blob/master/docs/overview.md) -* [Motions](https://github.com/atom/vim-mode/blob/master/docs/motions.md) +* [Motions and Text Objects](https://github.com/atom/vim-mode/blob/master/docs/motions.md) * [Operators](https://github.com/atom/vim-mode/blob/master/docs/operators.md) * [Windows](https://github.com/atom/vim-mode/blob/master/docs/windows.md) * [Scrolling](https://github.com/atom/vim-mode/blob/master/docs/scrolling.md) diff --git a/docs/motions.md b/docs/motions.md index 77442e67..ab77fddd 100644 --- a/docs/motions.md +++ b/docs/motions.md @@ -35,3 +35,25 @@ * [;](http://vimhelp.appspot.com/motion.txt.html#%3B) * [,](http://vimhelp.appspot.com/motion.txt.html#%2C) * [/ and ?](http://vimhelp.appspot.com/pattern.txt.html#search-commands) (including `//` and `??`) + +## Implemented Text Objects + +[Vim text objects](http://vimhelp.appspot.com/motion.txt.html#object-select) + +* ``a "`` ``i "`` +* ``a '`` ``i '`` +* ``a ``` ``i ``` +* ``a (`` ``i (`` +* ``a )`` ``i )`` +* ``a <`` ``i <`` +* ``a >`` ``i >`` +* ``a [`` ``i [`` +* ``a ]`` ``i ]`` +* ``a {`` ``i {`` +* ``a }`` ``i }`` +* ``a b`` ``i b`` +* ``a B`` ``i B`` +* ``a p`` ``i p`` +* ``a w`` ``i w`` +* ``a W`` ``i W`` +* ``i t`` diff --git a/keymaps/vim-mode.cson b/keymaps/vim-mode.cson index cfa54eb1..d56da193 100644 --- a/keymaps/vim-mode.cson +++ b/keymaps/vim-mode.cson @@ -286,6 +286,7 @@ 'atom-text-editor.vim-mode.operator-pending-mode, atom-text-editor.vim-mode.visual-mode': 'i w': 'vim-mode:select-inside-word' + 'i W': 'vim-mode:select-inside-whole-word' 'i "': 'vim-mode:select-inside-double-quotes' 'i \'': 'vim-mode:select-inside-single-quotes' 'i `': 'vim-mode:select-inside-back-ticks' @@ -302,6 +303,7 @@ 'i b': 'vim-mode:select-inside-parentheses' 'i p': 'vim-mode:select-inside-paragraph' 'a w': 'vim-mode:select-a-word' + 'a W': 'vim-mode:select-a-whole-word' 'a "': 'vim-mode:select-around-double-quotes' 'a \'': 'vim-mode:select-around-single-quotes' 'a `': 'vim-mode:select-around-back-ticks' diff --git a/lib/text-objects.coffee b/lib/text-objects.coffee index 6d9741b9..98289629 100644 --- a/lib/text-objects.coffee +++ b/lib/text-objects.coffee @@ -1,5 +1,6 @@ {Range} = require 'atom' AllWhitespace = /^\s$/ +WholeWordRegex = /\S+/ class TextObject constructor: (@editor, @state) -> @@ -12,6 +13,13 @@ class SelectInsideWord extends TextObject @editor.selectWordsContainingCursors() [true] +class SelectInsideWholeWord extends TextObject + select: -> + for selection in @editor.getSelections() + range = selection.cursor.getCurrentWordBufferRange({wordRegex: WholeWordRegex}) + selection.setBufferRange(range) + true + # SelectInsideQuotes and the next class defined (SelectInsideBrackets) are # almost-but-not-quite-repeated code. They are different because of the depth # checks in the bracket matcher. @@ -140,6 +148,18 @@ class SelectAWord extends TextObject selection.selectRight() true +class SelectAWholeWord extends TextObject + select: -> + for selection in @editor.getSelections() + range = selection.cursor.getCurrentWordBufferRange({wordRegex: WholeWordRegex}) + selection.setBufferRange(range) + loop + endPoint = selection.getBufferRange().end + char = @editor.getTextInRange(Range.fromPointWithDelta(endPoint, 0, 1)) + break unless AllWhitespace.test(char) + selection.selectRight() + true + class SelectInsideParagraph extends TextObject constructor: (@editor, @inclusive) -> select: -> @@ -161,4 +181,5 @@ class SelectAParagraph extends TextObject selection.selectDown() true -module.exports = {TextObject, SelectInsideWord, SelectInsideQuotes, SelectInsideBrackets, SelectAWord, SelectInsideParagraph, SelectAParagraph} +module.exports = {TextObject, SelectInsideWord, SelectInsideWholeWord, SelectInsideQuotes, + SelectInsideBrackets, SelectAWord, SelectAWholeWord, SelectInsideParagraph, SelectAParagraph} diff --git a/lib/vim-state.coffee b/lib/vim-state.coffee index 1daec292..63c83e3b 100644 --- a/lib/vim-state.coffee +++ b/lib/vim-state.coffee @@ -140,6 +140,7 @@ class VimState 'scroll-cursor-to-left': => new Scroll.ScrollCursorToLeft(@editorElement) 'scroll-cursor-to-right': => new Scroll.ScrollCursorToRight(@editorElement) 'select-inside-word': => new TextObjects.SelectInsideWord(@editor) + 'select-inside-whole-word': => new TextObjects.SelectInsideWholeWord(@editor) 'select-inside-double-quotes': => new TextObjects.SelectInsideQuotes(@editor, '"', false) 'select-inside-single-quotes': => new TextObjects.SelectInsideQuotes(@editor, '\'', false) 'select-inside-back-ticks': => new TextObjects.SelectInsideQuotes(@editor, '`', false) @@ -150,6 +151,7 @@ class VimState 'select-inside-parentheses': => new TextObjects.SelectInsideBrackets(@editor, '(', ')', false) 'select-inside-paragraph': => new TextObjects.SelectInsideParagraph(@editor, false) 'select-a-word': => new TextObjects.SelectAWord(@editor) + 'select-a-whole-word': => new TextObjects.SelectAWholeWord(@editor) 'select-around-double-quotes': => new TextObjects.SelectInsideQuotes(@editor, '"', true) 'select-around-single-quotes': => new TextObjects.SelectInsideQuotes(@editor, '\'', true) 'select-around-back-ticks': => new TextObjects.SelectInsideQuotes(@editor, '`', true) diff --git a/spec/text-objects-spec.coffee b/spec/text-objects-spec.coffee index 69ae6cbc..b6dcddbb 100644 --- a/spec/text-objects-spec.coffee +++ b/spec/text-objects-spec.coffee @@ -54,6 +54,29 @@ describe "TextObjects", -> [[0, 0], [0, 5]] ] + describe "the 'iW' text object", -> + beforeEach -> + editor.setText("12(45 ab'de ABCDE") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators inside the current whole word in operator-pending mode", -> + keydown('d') + keydown('i') + keydown('W', shift: true) + + expect(editor.getText()).toBe "12(45 ABCDE" + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + expect(vimState.getRegister('"').text).toBe "ab'de" + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('normal-mode')).toBe(true) + + it "selects inside the current whole word in visual mode", -> + keydown('v') + keydown('i') + keydown('W', shift: true) + + expect(editor.getSelectedScreenRange()).toEqual [[0, 6], [0, 11]] + describe "the 'i(' text object", -> beforeEach -> editor.setText("( something in here and in (here) )") @@ -211,7 +234,7 @@ describe "TextObjects", -> keydown('p') expect(editor.getSelectedScreenRange()).toEqual [[2, 0], [6, 0]] - + describe "the 'i[' text object", -> beforeEach -> editor.setText("[ something in here and in [here] ]") @@ -336,6 +359,49 @@ describe "TextObjects", -> expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 5]]] + it "doesn't span special characters", -> + editor.setText("1(345\nabcde ABCDE") + editor.setCursorBufferPosition([0, 3]) + + keydown("v") + keydown("a") + keydown("w") + + expect(editor.getSelectedBufferRanges()).toEqual [[[0, 2], [0, 5]]] + + describe "the 'aW' text object", -> + beforeEach -> + editor.setText("12(45 ab'de ABCDE") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators from the start of the current whole word to the start of the next whole word in operator-pending mode", -> + keydown('d') + keydown('a') + keydown('W', shift: true) + + expect(editor.getText()).toBe "12(45 ABCDE" + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + expect(vimState.getRegister('"').text).toBe "ab'de " + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('normal-mode')).toBe(true) + + it "selects from the start of the current whole word to the start of the next whole word in visual mode", -> + keydown('v') + keydown('a') + keydown('W', shift: true) + + expect(editor.getSelectedScreenRange()).toEqual [[0, 6], [0, 12]] + + it "doesn't span newlines", -> + editor.setText("12(45\nab'de ABCDE") + editor.setCursorBufferPosition([0, 4]) + + keydown('v') + keydown('a') + keydown('W', shift: true) + + expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 5]]] + describe "the 'a(' text object", -> beforeEach -> editor.setText("( something in here and in (here) )")