├── .gitignore ├── .travis.yml ├── LICENSE.md ├── Makefile ├── README.md ├── after └── ftplugin │ └── python │ └── textobj-python.vim ├── autoload └── textobj │ └── python.vim └── test ├── run ├── test.py ├── textobj-python.vader └── vimrc /.gitignore: -------------------------------------------------------------------------------- 1 | test/vader.vim 2 | test/vim-textobj-user 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: vim 2 | 3 | script: | 4 | make test 5 | 6 | sudo: false 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining 2 | a copy of this software and associated documentation files (the 3 | "Software"), to deal in the Software without restriction, including 4 | without limitation the rights to use, copy, modify, merge, publish, 5 | distribute, sublicense, and/or sell copies of the Software, and to 6 | permit persons to whom the Software is furnished to do so, subject to 7 | the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included 10 | in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 13 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 15 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 16 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 17 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 18 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | test/run 3 | .PHONY: test 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-textobj-python 2 | 3 | This is a Vim plugin to provide text objects for Python functions and classes. 4 | It provides the following objects: 5 | 6 | - `af`: a function 7 | - `if`: inner function 8 | - `ac`: a class 9 | - `ic`: inner class 10 | 11 | It also provides a few motions in normal and operator-pending mode: 12 | 13 | - `[pf` / `]pf`: move to next/previous function 14 | - `[pc` / `]pc`: move to next/previous class 15 | 16 | ## Installation 17 | 18 | It requires kana's [vim-textobj-user](https://github.com/kana/vim-textobj-user), 19 | version 0.4.0 or later. I recommend installing both `vim-textobj-user` and 20 | `vim-textobj-python` with [pathogen](https://github.com/tpope/vim-pathogen). 21 | 22 | ## Configuration 23 | 24 | If you'd like to change the default mappings (whether for personal preference 25 | or to avoid conflicts with other plugins, define a global variable 26 | `g:textobj_python_no_default_key_mappings` before this plugin is loaded 27 | (typically in your vimrc): 28 | 29 | let g:textobj_python_no_default_key_mappings = 1 30 | 31 | Then define the mappings with the helper function: 32 | 33 | call textobj#user#map('python', { 34 | \ 'class': { 35 | \ 'select-a': 'ac', 36 | \ 'select-i': 'ic', 37 | \ 'move-n': ']pc', 38 | \ 'move-p': '[pc', 39 | \ }, 40 | \ 'function': { 41 | \ 'select-a': 'af', 42 | \ 'select-i': 'if', 43 | \ 'move-n': ']pf', 44 | \ 'move-p': '[pf', 45 | \ } 46 | \ }) 47 | 48 | You can also use `:TextobjPythonDefaultKeyMappings` to redefine the default 49 | key mappings. This command doesn't override existing mappings, unless `[!]` is 50 | given. 51 | 52 | You can also manually define the mappings as follows: 53 | 54 | xmap aF (textobj-python-function-a) 55 | omap aF (textobj-python-function-a) 56 | xmap iF (textobj-python-function-i) 57 | omap iF (textobj-python-function-i) 58 | 59 | 60 | ## Unit tests 61 | 62 | There are a few unit tests available in `test/`. They're written using 63 | [Vader](https://github.com/junegunn/vader.vim). 64 | 65 | [![Build Status](https://travis-ci.org/bps/vim-textobj-python.svg?branch=vader-tests)](https://travis-ci.org/bps/vim-textobj-python) 66 | 67 | ## TODO 68 | 69 | - Skip improperly-indented comment lines 70 | - Handle inner function on multiline def one-liner: 71 | 72 | ```python 73 | def foo(bar, 74 | baz): pass 75 | ``` 76 | -------------------------------------------------------------------------------- /after/ftplugin/python/textobj-python.vim: -------------------------------------------------------------------------------- 1 | " Text objects for Python 2 | " Version 0.4.1 3 | " Copyright (C) 2013 Brian Smyth 4 | " License: So-called MIT license {{{ 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be included 14 | " in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | " }}} 24 | 25 | " Integration with https://github.com/kana/vim-textobj-function. 26 | if !exists('b:textobj_function_select') 27 | let b:textobj_function_select = function('textobj#python#function_select') 28 | 29 | if exists('b:undo_ftplugin') 30 | let b:undo_ftplugin .= '|' 31 | else 32 | let b:undo_ftplugin = '' 33 | endif 34 | let b:undo_ftplugin .= 'unlet b:textobj_function_select' 35 | endif 36 | 37 | 38 | if exists('g:loaded_textobj_python') 39 | finish 40 | endif 41 | 42 | call textobj#user#plugin('python', { 43 | \ 'class': { 44 | \ 'sfile': expand(':p'), 45 | \ 'select-a': 'ac', 46 | \ 'select-i': 'ic', 47 | \ 'select-a-function': 'textobj#python#class_select_a', 48 | \ 'select-i-function': 'textobj#python#class_select_i', 49 | \ 'pattern': '^\s*\zsclass \(.\|\n\)\{-}:', 50 | \ 'move-p': '[pc', 51 | \ 'move-n': ']pc', 52 | \ }, 53 | \ 'function': { 54 | \ 'sfile': expand(':p'), 55 | \ 'select-a': 'af', 56 | \ 'select-i': 'if', 57 | \ 'select-a-function': 'textobj#python#function_select_a', 58 | \ 'select-i-function': 'textobj#python#function_select_i', 59 | \ 'pattern': '^\s*\zs\(def\|async def\) \(.\|\n\)\{-}:', 60 | \ 'move-p': '[pf', 61 | \ 'move-n': ']pf', 62 | \ } 63 | \}) 64 | 65 | -------------------------------------------------------------------------------- /autoload/textobj/python.vim: -------------------------------------------------------------------------------- 1 | " Text objects for Python 2 | " Version 0.4.1 3 | " Copyright (C) 2013 Brian Smyth 4 | " License: So-called MIT license {{{ 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be included 14 | " in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | " }}} 24 | 25 | function! textobj#python#move_cursor_to_starting_line() 26 | " Start at a nonblank line 27 | let l:cur_pos = getpos('.') 28 | let l:cur_line = getline('.') 29 | if l:cur_line =~# '^\s*$' 30 | call cursor(prevnonblank(l:cur_pos[1]), 0) 31 | endif 32 | endfunction 33 | 34 | function! textobj#python#find_defn_line(kwd) 35 | 36 | " Skip decorators 37 | while getline('.') !~# '^\s*'.a:kwd.' ' 38 | " Avoid skipping decorator under the cursor 39 | normal! $ 40 | let l:cur_pos = getpos('.') 41 | let l:decorator = search('^\s*@.*(\?', 'bW') 42 | if l:decorator == 0 43 | break 44 | endif 45 | " Match r-parent if any 46 | normal! 0 47 | normal! % 48 | normal! j 49 | let l:new_pos = getpos('.') 50 | if l:new_pos[1] <= l:cur_pos[1] 51 | call setpos('.', l:cur_pos) 52 | break 53 | endif 54 | endwhile 55 | 56 | let l:cur_line = getline('.') 57 | 58 | let l:cur_pos = getpos('.') 59 | if l:cur_line =~# '^\s*'.a:kwd.' ' 60 | let l:defn_pos = l:cur_pos 61 | else 62 | let l:cur_indent = indent(l:cur_pos[1]) 63 | while 1 64 | if search('^\s*'.a:kwd.' ', 'bW') 65 | let l:defn_pos = getpos('.') 66 | let l:defn_indent = indent(l:defn_pos[1]) 67 | if l:defn_indent >= l:cur_indent 68 | " This is a defn at the same level or deeper, keep searching 69 | continue 70 | else 71 | " Found a defn, make sure there aren't any statements at a 72 | " shallower indent level in between 73 | for l:l in range(l:defn_pos[1] + 1, l:cur_pos[1]) 74 | if getline(l:l) !~# '^\s*$' && indent(l:l) <= l:defn_indent 75 | throw "defn-not-found" 76 | endif 77 | endfor 78 | break 79 | endif 80 | else 81 | " We didn't find a suitable defn 82 | throw "defn-not-found" 83 | endif 84 | endwhile 85 | endif 86 | call cursor(defn_pos) 87 | return l:defn_pos 88 | endfunction 89 | 90 | function! textobj#python#find_prev_decorators(l) 91 | " Find the line with the first (valid) decorator above `line`, return the 92 | " current line, if there is none. 93 | let l:linenr = a:l 94 | let l:line_ident = indent(l:linenr) 95 | while 1 96 | " Get the first not blank line 97 | let l:prev = prevnonblank(l:linenr - 1) 98 | if l:prev == 0 99 | " There is not above current one. 100 | break 101 | endif 102 | 103 | " Move cursor 104 | let l:prev_pos = getpos('.') 105 | let l:prev_pos[1] = l:prev 106 | call setpos('.', l:prev_pos) 107 | 108 | let l:prev_indent = indent(l:prev_pos[1]) 109 | if l:prev_indent < l:line_ident 110 | " Indentention isn't valid for a decorator 111 | break 112 | endif 113 | 114 | " Match l-parent if any 115 | normal! $ 116 | normal! % 117 | 118 | let l:prev_pos = getpos('.') 119 | 120 | " The decorator should be in the same level 121 | " as the original class/function. 122 | if getline(l:prev_pos[1])[l:line_ident] != "@" 123 | break 124 | endif 125 | let l:linenr = l:prev_pos[1] 126 | endwhile 127 | return l:linenr 128 | endfunction 129 | 130 | function! textobj#python#find_last_line(kwd, defn_pos, indent_level) 131 | " Find the last line of the block at given indent level 132 | let l:cur_pos = getpos('.') 133 | let l:end_pos = l:cur_pos 134 | while 1 135 | " Is this a one-liner? 136 | if getline('.') =~# '^\s*'.a:kwd.'\[^:\]\+:\s*\[^#\]' 137 | return a:defn_pos 138 | endif 139 | " This isn't a one-liner, so skip the def line 140 | if line('.') == a:defn_pos[1] 141 | normal! j 142 | continue 143 | endif 144 | if getline('.') !~# '^\s*$' 145 | if indent('.') > a:indent_level 146 | let l:end_pos = getpos('.') 147 | else 148 | break 149 | endif 150 | endif 151 | if line('.') == line('$') 152 | break 153 | else 154 | normal! j 155 | endif 156 | endwhile 157 | call cursor(l:cur_pos[1], l:cur_pos[2]) 158 | return l:end_pos 159 | endfunction 160 | 161 | function! s:find_defn(kwd) 162 | call textobj#python#move_cursor_to_starting_line() 163 | 164 | try 165 | let l:defn_pos = textobj#python#find_defn_line(a:kwd) 166 | catch /defn-not-found/ 167 | return 0 168 | endtry 169 | let l:defn_indent_level = indent(l:defn_pos[1]) 170 | 171 | let l:end_pos = textobj#python#find_last_line(a:kwd, l:defn_pos, l:defn_indent_level) 172 | return ['V', l:defn_pos, l:end_pos] 173 | endfunction 174 | 175 | function! s:select_surrounding_blank_lines(pos) 176 | let l:defn_pos = copy(a:pos) 177 | 178 | let l:blanks_on_start = l:defn_pos[1][1] - (prevnonblank(l:defn_pos[1][1] - 1) + 1) 179 | let l:current_block_indent_level = indent(l:defn_pos[1][1]) 180 | let l:next_block_linenr = nextnonblank(l:defn_pos[2][1] + 1) 181 | let l:next_block_indent_level = indent(l:next_block_linenr) 182 | 183 | if l:next_block_linenr != 0 184 | if l:current_block_indent_level != 0 && l:next_block_indent_level == 0 185 | let l:desired_blanks = 2 186 | let l:desired_blanks = max([0, l:desired_blanks - l:blanks_on_start]) 187 | let l:defn_pos[2][1] = l:next_block_linenr - 1 - l:desired_blanks 188 | else 189 | let l:defn_pos[2][1] = l:next_block_linenr - 1 190 | endif 191 | else 192 | let l:defn_pos[1][1] = prevnonblank(l:defn_pos[1][1] - 1) + 1 193 | endif 194 | return l:defn_pos 195 | endfunction 196 | 197 | function! textobj#python#select_a(kwd) 198 | let l:defn_pos = s:find_defn(a:kwd) 199 | if type(l:defn_pos) == type([]) 200 | let l:defn_pos[1][1] = textobj#python#find_prev_decorators(l:defn_pos[1][1]) 201 | let l:defn_pos = s:select_surrounding_blank_lines(l:defn_pos) 202 | return l:defn_pos 203 | endif 204 | return 0 205 | endfunction 206 | 207 | function! textobj#python#select_i(kwd) 208 | let l:a_pos = s:find_defn(a:kwd) 209 | if type(l:a_pos) == type([]) 210 | if l:a_pos[1][1] == l:a_pos[2][1] 211 | " This is a one-liner, treat it like af 212 | " TODO Maybe change this to a 'v'-mode selection and only get the 213 | " statement from the one-liner? 214 | return l:a_pos 215 | endif 216 | " Put the cursor on the def line 217 | call cursor(l:a_pos[1][1], l:a_pos[1][2]) 218 | " Get to the closing parenthesis if it exists 219 | normal! ^f(% 220 | " Start from the beginning of the next line 221 | normal! j0 222 | let l:start_pos = getpos('.') 223 | return ['V', l:start_pos, l:a_pos[2]] 224 | endif 225 | return 0 226 | endfunction 227 | 228 | function! textobj#python#class_select_a() 229 | return textobj#python#select_a('class') 230 | endfunction 231 | 232 | function! textobj#python#class_select_i() 233 | return textobj#python#select_i('class') 234 | endfunction 235 | 236 | function! textobj#python#function_select_a() 237 | return textobj#python#select_a('\(async def\|def\)') 238 | endfunction 239 | 240 | function! textobj#python#function_select_i() 241 | return textobj#python#select_i('\(async def\|def\)') 242 | endfunction 243 | 244 | function! textobj#python#function_select(object_type) 245 | return textobj#python#function_select_{a:object_type}() 246 | endfunction 247 | -------------------------------------------------------------------------------- /test/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Change to script's directory. 4 | CDPATH= cd -P -- "$(dirname -- "$0")" 5 | 6 | # Look for existing vader and textobj-user installations, and link them. 7 | link_or_clone() { 8 | # set -x 9 | glob=$1 10 | url=$2 11 | target=$(basename $url) 12 | 13 | if [[ -e $target ]]; then 14 | return 15 | fi 16 | 17 | existing=( ${HOME}/.vim/*bundle*/$glob ) 18 | if [[ -e ${existing[0]} ]]; then 19 | # Remove "(plugin|autoload)/*.vim" suffix. 20 | existing=${existing[0]%/*/*} 21 | fi 22 | 23 | if [[ -d $existing ]]; then 24 | [[ ! -L $target ]] && ln -s $existing $target 25 | else 26 | git clone $url 27 | fi 28 | # set +x 29 | } 30 | 31 | link_or_clone 'vader*/plugin/vader.vim' https://github.com/junegunn/vader.vim 32 | link_or_clone '*textobj-user/autoload/textobj' https://github.com/kana/vim-textobj-user 33 | 34 | vim -XNu vimrc -i NONE -c 'Vader! *' 35 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | # Test scenarios for textobj-python 2 | 3 | def regular_func(bar, baz): 4 | print(bar) 5 | 6 | print(baz) 7 | print("quux") 8 | 9 | 10 | print("other_stmts") 11 | 12 | 13 | def multiline_func_def(baz, 14 | quux): 15 | print(baz) 16 | print(quux) 17 | 18 | print("barp") 19 | 20 | 21 | def oneliner(): pass 22 | 23 | 24 | def multiline_def_oneliner(asdf, 25 | qwer): pass 26 | 27 | 28 | def nested_func(): 29 | print("foo") 30 | 31 | def the_inner_func(): 32 | print("bar") 33 | print("foo") 34 | print("baz") 35 | 36 | 37 | class TrySomething(): 38 | def foo(self): 39 | try: 40 | print("bar") 41 | except Exception as e: 42 | print(e) 43 | finally: 44 | print("finally") 45 | 46 | 47 | class RegularClass(): 48 | def __init__(self): 49 | print("asdf") 50 | pass 51 | 52 | def bar(self): 53 | print(self.bar) 54 | 55 | 56 | class NestedClass(): 57 | class InHere(): 58 | pass 59 | 60 | 61 | def this_func_has_a_func(): 62 | def foo(): 63 | pass 64 | 65 | class InsideFunc(): 66 | def __init__(self): 67 | pass 68 | 69 | def foo(self): 70 | print(foo) 71 | 72 | 73 | def one_stmt(): 74 | pass 75 | 76 | 77 | class no_defs(): 78 | """This is a long comment. 79 | 80 | Do a vif here, make sure it gets nothing. 81 | 82 | """ 83 | pass 84 | 85 | 86 | class NoAncestor: 87 | def foo(self): 88 | pass 89 | 90 | def bar(self): 91 | pass 92 | 93 | 94 | def decorator(f, *args): 95 | pass 96 | 97 | @decorator 98 | class ClassWithDecorator: 99 | pass 100 | 101 | 102 | @decorator 103 | @decorator(1, 2) 104 | class ClassWithDecorators: 105 | # ClassWithDecorator 106 | pass 107 | 108 | 109 | @decorator 110 | def function_with_decorator(): 111 | pass 112 | 113 | 114 | @decorator 115 | @decorator(1, 2) 116 | def function_with_decorators(): 117 | # function_with_decorators 118 | pass 119 | 120 | 121 | async def function_name(foo): 122 | # asynchronous function 123 | pass 124 | 125 | 126 | @decorator 127 | @decorator('one', 'two') 128 | @decorator(1, 2) 129 | @decorator( 130 | 1, 131 | 2, 132 | ) 133 | @decorator( 134 | 1, 135 | # comment 136 | 2, 137 | ) 138 | def function_with_multiline_decorators(): 139 | # function_with_multiline_decorators 140 | pass 141 | 142 | @decorator 143 | @decorator('one', 'two') 144 | @decorator(1, 2) 145 | @decorator( 146 | 1, 147 | 2, 148 | ) 149 | @decorator( 150 | 1, 151 | # comment 152 | 2, 153 | ) 154 | class ClassWithMultilineDecorators: 155 | # ClassWithMultilineDecorators 156 | pass 157 | 158 | 159 | class RegularClass(): 160 | def foo(self): 161 | pass 162 | 163 | @decorator 164 | def method_with_decorator(self): 165 | pass 166 | 167 | 168 | def at_end_of_file(): 169 | pass 170 | -------------------------------------------------------------------------------- /test/textobj-python.vader: -------------------------------------------------------------------------------- 1 | Before (Read in test.py): 2 | execute "read " . globpath(fnamemodify(g:vader_file, ":h"), "test.py") 3 | setf python 4 | normal 1Gdd 5 | 6 | Execute (Regular inner function): 7 | call cursor(4, 5) 8 | normal vif 9 | AssertEqual 4, getpos("'<")[1] 10 | AssertEqual 7, getpos("'>")[1] 11 | 12 | Execute (Regular a function): 13 | call cursor(4,5) 14 | normal vaf 15 | AssertEqual 3, getpos("'<")[1] 16 | AssertEqual 9, getpos("'>")[1] 17 | 18 | Execute (Multiline defn inner function): 19 | call cursor(15, 11) 20 | normal vif 21 | AssertEqual 15, getpos("'<")[1] 22 | AssertEqual 18, getpos("'>")[1] 23 | 24 | Execute (Multiline defn a function): 25 | call cursor(15, 11) 26 | normal vaf 27 | AssertEqual 13, getpos("'<")[1] 28 | AssertEqual 20, getpos("'>")[1] 29 | 30 | Execute (One-liner inner function): 31 | call cursor(21, 12) 32 | normal vif 33 | AssertEqual 21, getpos("'<")[1] 34 | AssertEqual 21, getpos("'>")[1] 35 | 36 | Execute (One-liner a function): 37 | call cursor(21, 12) 38 | normal vaf 39 | AssertEqual 21, getpos("'<")[1] 40 | AssertEqual 23, getpos("'>")[1] 41 | 42 | Execute (FIXME Multiline defn one-liner inner function): 43 | call cursor(24, 5) 44 | normal vif 45 | AssertEqual 24, getpos("'<")[1] 46 | AssertEqual 25, getpos("'>")[1] 47 | 48 | Execute (Multiline defn one-liner a function): 49 | call cursor(24, 5) 50 | normal vaf 51 | AssertEqual 24, getpos("'<")[1] 52 | AssertEqual 27, getpos("'>")[1] 53 | 54 | Execute (Outer nested function inner function): 55 | call cursor(29, 1) 56 | normal vif 57 | AssertEqual 29, getpos("'<")[1] 58 | AssertEqual 34, getpos("'>")[1] 59 | 60 | Execute (Outer nested function a function): 61 | call cursor(29, 1) 62 | normal vaf 63 | AssertEqual 28, getpos("'<")[1] 64 | AssertEqual 36, getpos("'>")[1] 65 | 66 | Execute (Inner nested function inner function): 67 | call cursor(32, 1) 68 | normal vif 69 | AssertEqual 32, getpos("'<")[1] 70 | AssertEqual 33, getpos("'>")[1] 71 | 72 | Execute (Inner nested function a function): 73 | call cursor(32, 1) 74 | normal vaf 75 | AssertEqual 31, getpos("'<")[1] 76 | AssertEqual 33, getpos("'>")[1] 77 | 78 | Execute (Inner asynchronous function): 79 | call cursor(122, 1) 80 | normal vif 81 | AssertEqual 122, getpos("'<")[1] 82 | AssertEqual 123, getpos("'>")[1] 83 | 84 | Execute (Outer asynchronous function): 85 | call cursor(122, 1) 86 | normal vaf 87 | AssertEqual 121, getpos("'<")[1] 88 | AssertEqual 125, getpos("'>")[1] 89 | 90 | Execute (Simple inner class): 91 | call cursor(41, 1) 92 | normal vic 93 | AssertEqual 38, getpos("'<")[1] 94 | AssertEqual 44, getpos("'>")[1] 95 | 96 | Execute (Simple a class): 97 | call cursor(41, 1) 98 | normal vac 99 | AssertEqual 37, getpos("'<")[1] 100 | AssertEqual 46, getpos("'>")[1] 101 | 102 | Execute (Multiple members inner class): 103 | call cursor(52, 1) 104 | normal vic 105 | AssertEqual 48, getpos("'<")[1] 106 | AssertEqual 53, getpos("'>")[1] 107 | 108 | Execute (Multiple members a class): 109 | call cursor(52, 1) 110 | normal vac 111 | AssertEqual 47, getpos("'<")[1] 112 | AssertEqual 55, getpos("'>")[1] 113 | 114 | Execute (Nested classes outer inner class): 115 | call cursor(56, 1) 116 | normal vic 117 | AssertEqual 57, getpos("'<")[1] 118 | AssertEqual 58, getpos("'>")[1] 119 | 120 | Execute (Nested classes outer a class): 121 | call cursor(56, 1) 122 | normal vac 123 | AssertEqual 56, getpos("'<")[1] 124 | AssertEqual 60, getpos("'>")[1] 125 | 126 | Execute (Nested classes inner inner class): 127 | call cursor(57, 1) 128 | normal vic 129 | AssertEqual 58, getpos("'<")[1] 130 | AssertEqual 58, getpos("'>")[1] 131 | 132 | Execute (Nested classes inner a class): 133 | call cursor(57, 1) 134 | normal vac 135 | AssertEqual 57, getpos("'<")[1] 136 | AssertEqual 58, getpos("'>")[1] 137 | 138 | Execute (Class nested in function inner class): 139 | call cursor(65, 1) 140 | normal vic 141 | AssertEqual 66, getpos("'<")[1] 142 | AssertEqual 70, getpos("'>")[1] 143 | 144 | Execute (Class nested in function a class): 145 | call cursor(65, 1) 146 | normal vac 147 | AssertEqual 65, getpos("'<")[1] 148 | AssertEqual 71, getpos("'>")[1] 149 | 150 | Execute (Function while in class nested in function inner class): 151 | call cursor(65, 1) 152 | normal vif 153 | AssertEqual 62, getpos("'<")[1] 154 | AssertEqual 70, getpos("'>")[1] 155 | 156 | Execute (Function while in class nested in function a class): 157 | call cursor(65, 1) 158 | normal vaf 159 | AssertEqual 61, getpos("'<")[1] 160 | AssertEqual 72, getpos("'>")[1] 161 | 162 | Execute (Old-style no ancestor class inner class): 163 | call cursor(86, 9) 164 | normal vic 165 | AssertEqual 87, getpos("'<")[1] 166 | AssertEqual 91, getpos("'>")[1] 167 | 168 | Execute (Old-style no ancestor class a class): 169 | call cursor(86, 9) 170 | normal vac 171 | AssertEqual 86, getpos("'<")[1] 172 | AssertEqual 93, getpos("'>")[1] 173 | 174 | Execute (Don't cross scope for defn): 175 | /^class no_defs 176 | norm j 177 | let linenr = getpos(".")[1] 178 | norm vaf 179 | AssertEqual getpos(".")[1], linenr, "The line should not have changed." 180 | AssertThrows textobj#python#find_defn_line("def") 181 | 182 | Execute (Method with decorator): 183 | /^ def method_with_decorator( 184 | norm vaf 185 | AssertEqual getpos("'<")[1], 163 186 | AssertEqual getpos("'>")[1], 166 187 | 188 | Execute (At end of file, select prior whitespace): 189 | /^def at_end_of_file( 190 | norm vaf 191 | AssertEqual getpos("'<")[1], line("$") - 3 192 | AssertEqual getpos("'>")[1], line("$") 193 | 194 | Execute (Decorator: function): 195 | /^def function_with_decorator( 196 | let linenr = getpos(".")[1] 197 | " debug call textobj#python#find_prev_decorators(linenr) 198 | AssertEqual linenr-1, textobj#python#find_prev_decorators(linenr) 199 | " Move cursor back 200 | /^def function_with_decorator( 201 | norm jvaf 202 | AssertEqual getline("."), "@decorator" 203 | AssertEqual getpos("'>")[1], linenr + 3 204 | 205 | Execute (Decorators: function): 206 | /^def function_with_decorators( 207 | let linenr = getpos(".")[1] 208 | norm jvaf 209 | AssertEqual getline("."), "@decorator" 210 | AssertEqual getpos("'>")[1], linenr + 4 211 | 212 | Execute (Decorators: inner function): 213 | /^def function_with_decorators( 214 | norm jvif 215 | AssertEqual getline("."), "# function_with_decorators" 216 | AssertEqual getline("'>"), "pass" 217 | 218 | Execute (Decorators: inner function from decorator): 219 | /^def function_with_decorators( 220 | norm kvif 221 | AssertEqual getline("."), "# function_with_decorators" 222 | AssertEqual getline("'>"), "pass" 223 | 224 | Execute (Decorators: inner function from decorators with multiple lines): 225 | /^def function_with_multiline_decorators( 226 | norm kvif 227 | AssertEqual getline("."), "# function_with_multiline_decorators" 228 | AssertEqual getline("'>"), "pass" 229 | 230 | Execute (Decorators: inner function from decorators with multiple lines (2)): 231 | /^def function_with_multiline_decorators( 232 | norm 2kvif 233 | AssertEqual getline("."), "# function_with_multiline_decorators" 234 | AssertEqual getline("'>"), "pass" 235 | 236 | Execute (Decorators: inner function from decorators with multiple lines (3)): 237 | /^def function_with_multiline_decorators( 238 | norm 9kvif 239 | AssertEqual getline("."), "# function_with_multiline_decorators" 240 | AssertEqual getline("'>"), "pass" 241 | 242 | Execute (Decorators: inner function from decorators with multiple lines (4)): 243 | /^def function_with_multiline_decorators( 244 | norm 10kvif 245 | AssertEqual getline("."), "# function_with_multiline_decorators" 246 | AssertEqual getline("'>"), "pass" 247 | 248 | Execute (Decorators: inner function from decorators with multiple lines (5)): 249 | /^def function_with_multiline_decorators( 250 | norm 11kvif 251 | AssertEqual getline("."), "# function_with_multiline_decorators" 252 | AssertEqual getline("'>"), "pass" 253 | 254 | Execute (Decorators: inner function from decorators with multiple lines (6)): 255 | /^def function_with_multiline_decorators( 256 | norm 12kvif 257 | AssertEqual getline("."), "# function_with_multiline_decorators" 258 | AssertEqual getline("'>"), "pass" 259 | 260 | Execute (Decorators: function with decorators with multiple lines): 261 | /^def function_with_multiline_decorators( 262 | let linenr = getpos(".")[1] 263 | norm kvaf 264 | AssertEqual getline("."), "@decorator" 265 | AssertEqual getpos("'>")[1], linenr + 3 266 | 267 | Execute (Decorators: a function from decorator): 268 | /^def function_with_decorators( 269 | let linenr = getpos(".")[1] 270 | norm kvaf 271 | AssertEqual getline("."), "@decorator" 272 | AssertEqual getpos("'>")[1], linenr + 4 273 | 274 | Execute (Decorator: class): 275 | /^class ClassWithDecorator: 276 | let linenr = getpos(".")[1] 277 | AssertEqual linenr-1, textobj#python#find_prev_decorators(linenr) 278 | " Move cursor back 279 | /^class ClassWithDecorator: 280 | norm jvac 281 | AssertEqual getline("."), "@decorator" 282 | AssertEqual getpos("'>")[1], linenr + 3 283 | 284 | Execute (Decorators: class): 285 | /^class ClassWithDecorators: 286 | let linenr = getpos(".")[1] 287 | norm jvac 288 | AssertEqual getline("."), "@decorator" 289 | AssertEqual getpos("'>")[1], linenr + 4 290 | 291 | Execute (Decorators: class from decorator): 292 | /^class ClassWithDecorators: 293 | let linenr = getpos(".")[1] 294 | norm kvac 295 | AssertEqual getline("."), "@decorator" 296 | AssertEqual getpos("'>")[1], linenr + 4 297 | 298 | Execute (Decorators: inner class from decorators with multiple lines): 299 | /^class ClassWithMultilineDecorators: 300 | norm kvic 301 | AssertEqual getline("."), "# ClassWithMultilineDecorators" 302 | AssertEqual getline("'>"), "pass" 303 | 304 | Execute (Decorators: inner class from decorators with multiple lines (2)): 305 | /^class ClassWithMultilineDecorators: 306 | norm 2kvic 307 | AssertEqual getline("."), "# ClassWithMultilineDecorators" 308 | AssertEqual getline("'>"), "pass" 309 | 310 | Execute (Decorators: inner class from decorators with multiple lines (3)): 311 | /^class ClassWithMultilineDecorators: 312 | norm 9kvic 313 | AssertEqual getline("."), "# ClassWithMultilineDecorators" 314 | AssertEqual getline("'>"), "pass" 315 | 316 | Execute (Decorators: inner class from decorators with multiple lines (4)): 317 | /^class ClassWithMultilineDecorators: 318 | norm 10kvic 319 | AssertEqual getline("."), "# ClassWithMultilineDecorators" 320 | AssertEqual getline("'>"), "pass" 321 | 322 | Execute (Decorators: inner class from decorators with multiple lines (5)): 323 | /^class ClassWithMultilineDecorators: 324 | norm 11kvic 325 | AssertEqual getline("."), "# ClassWithMultilineDecorators" 326 | AssertEqual getline("'>"), "pass" 327 | 328 | Execute (Decorators: inner class from decorators with multiple lines (6)): 329 | /^class ClassWithMultilineDecorators: 330 | norm 12kvic 331 | AssertEqual getline("."), "# ClassWithMultilineDecorators" 332 | AssertEqual getline("'>"), "pass" 333 | 334 | Execute (Decorators: class with decorators with multiple lines): 335 | /^class ClassWithMultilineDecorators: 336 | let linenr = getpos(".")[1] 337 | norm kvac 338 | AssertEqual getline("."), "@decorator" 339 | AssertEqual getpos("'>")[1], 158 340 | AssertEqual getpos("'>")[1], linenr + 4 341 | -------------------------------------------------------------------------------- /test/vimrc: -------------------------------------------------------------------------------- 1 | filetype off 2 | set rtp+=vader.vim 3 | set rtp+=vim-textobj-user 4 | set rtp+=.. 5 | set rtp+=../after 6 | filetype plugin indent on 7 | --------------------------------------------------------------------------------