├── after └── ftplugin │ └── ruby │ └── textobj-ruby.vim ├── plugin └── textobj │ └── ruby.vim ├── README.md ├── autoload └── textobj │ └── ruby.vim └── doc └── textobj-ruby.txt /after/ftplugin/ruby/textobj-ruby.vim: -------------------------------------------------------------------------------- 1 | if !get(g:, 'textobj_ruby_no_mappings', 0) 2 | omap ar (textobj-ruby-block-a) 3 | omap ir (textobj-ruby-block-i) 4 | omap ac (textobj-ruby-class-a) 5 | omap ic (textobj-ruby-class-i) 6 | omap af (textobj-ruby-function-a) 7 | omap if (textobj-ruby-function-i) 8 | omap an (textobj-ruby-name) 9 | omap in (textobj-ruby-name) 10 | 11 | xmap ar (textobj-ruby-block-a) 12 | xmap ir (textobj-ruby-block-i) 13 | xmap ac (textobj-ruby-class-a) 14 | xmap ic (textobj-ruby-class-i) 15 | xmap af (textobj-ruby-function-a) 16 | xmap if (textobj-ruby-function-i) 17 | xmap an (textobj-ruby-name) 18 | xmap in (textobj-ruby-name) 19 | endif 20 | -------------------------------------------------------------------------------- /plugin/textobj/ruby.vim: -------------------------------------------------------------------------------- 1 | let g:textobj_ruby_no_default_key_mappings = 1 2 | 3 | call textobj#user#plugin('ruby', { 4 | \ 'block': { 5 | \ 'sfile': expand(':p'), 6 | \ 'select-a': 'ar', 7 | \ 'select-a-function': 'textobj#ruby#a_block', 8 | \ 'select-i': 'ir', 9 | \ 'select-i-function': 'textobj#ruby#i_block', 10 | \ }, 11 | \ 'function': { 12 | \ 'sfile': expand(':p'), 13 | \ 'select-a': 'af', 14 | \ 'select-a-function': 'textobj#ruby#a_func', 15 | \ 'select-i': 'if', 16 | \ 'select-i-function': 'textobj#ruby#i_func', 17 | \ }, 18 | \ 'class': { 19 | \ 'sfile': expand(':p'), 20 | \ 'select-a': 'ac', 21 | \ 'select-a-function': 'textobj#ruby#a_class', 22 | \ 'select-i': 'ic', 23 | \ 'select-i-function': 'textobj#ruby#i_class', 24 | \ }, 25 | \ 'name': { 26 | \ 'pattern': '\w\+\%(::\w\+\)*', 27 | \ 'select': ['an', 'in'], 28 | \ }, 29 | \ }) 30 | 31 | if !exists('g:textobj_ruby_inclusive') 32 | let g:textobj_ruby_inclusive = 1 33 | endif 34 | 35 | if !exists('g:ruby_block_openers') 36 | let g:ruby_block_openers = '%(<%(def|if|module|class|until|begin|while|' . 37 | \ 'case|unless)>|.*)' 38 | endif 39 | 40 | if !exists('g:ruby_block_middles') 41 | let g:ruby_block_middles = '\v<%(els%(e|if)|rescue|ensure)>' 42 | endif 43 | 44 | if !exists('g:textobj_ruby_grow') 45 | let g:textobj_ruby_grow = 1 46 | endif 47 | 48 | if !exists('g:textobj_ruby_inner_branch') 49 | let g:textobj_ruby_inner_branch = 1 50 | endif 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | This defines four ruby text objects – block, class, function and name. 4 | A block is delimited by the regex in the variable `g:ruby_block_openers` and 5 | `end`. 6 | A function is a special case of block, starting with `def`, class works the 7 | same way. 8 | The inner variant doesn't include the delimiter lines. 9 | 10 | A name is a module-scoped identifier like `Module::Klass`. 11 | 12 | ## Usage 13 | 14 | By default, the omaps `ir` and `ar` are used for a block; `if` and `af` for a 15 | function, `ic`, `ac` for a class and `an` for a name. 16 | 17 | Specifying a count selects outer blocks recursively. 18 | 19 | ## Customization 20 | 21 | The variable `g:textobj_ruby_inclusive` controls whether leading comments and a 22 | single blank line are included in the `a` object if present, defaulting to 1. 23 | 24 | Consult the documentation of [textobj-user][1] for custom mappings. 25 | 26 | If `g:textobj_ruby_inner_branch` is `1`, the inner block object will select the 27 | lines between `else`, `elsif`, `rescue` and `ensure` (configurable via 28 | `g:ruby_block_middles`) and the boundaries instead of including all conditional 29 | branches. 30 | 31 | If `g:textobj_ruby_no_mappings` is `1`, no actual mappings are created. 32 | 33 | ## Details 34 | 35 | The plugin uses a consistent line based approach, with the lines containing the 36 | delimiters being part of the block. 37 | This means that invoking the text object on the word before a `do` will select 38 | the block opened by it, which is especially useful on empty blocks. 39 | When the `ir` object is used on the opener line, the selection will begin on 40 | the next line. 41 | 42 | The reason for inclusive mode is that when deleting or copying a function, you 43 | most likely want to paste it somewhere else and not leave an additional empty 44 | line around (given that you separate all functions by a single blank line). 45 | Comments directly above a block are most likely connected to it, so leaving 46 | them would be impractical. 47 | 48 | When invoked multiple times in visual mode, the selection grows to the next 49 | larger block, just like when invoking the operator with a count. 50 | To decide whether to grow, the visual selection is checked – if start and end 51 | do not match, it is assumed that the selection is already a block. 52 | This can result in faulty behaviour when invoked from visual mode with o 53 | nonzero selection and the cursor on the end delimiter line – the next outer 54 | block will be selected. 55 | 56 | ## Dependencies 57 | 58 | [textobj-user][1] 59 | 60 | ## License 61 | 62 | Copyright (c) Torsten Schmits. Distributed under the terms of the [MIT 63 | License][2]. 64 | 65 | [1]: https://github.com/kana/vim-textobj-user 'textobj-user' 66 | [2]: http://opensource.org/licenses/MIT 'mit license' 67 | -------------------------------------------------------------------------------- /autoload/textobj/ruby.vim: -------------------------------------------------------------------------------- 1 | let s:comment = '^\s*#' 2 | let s:space_or_eol = '\v\ze%($|\s)' 3 | let s:start = '\v^\zs%(.* \=)?\s*' . g:ruby_block_openers . s:space_or_eol 4 | let s:middle = '\v^\zs\s*' . g:ruby_block_middles . s:space_or_eol 5 | let s:end = '\v^\s*.*\zs' 6 | let s:flags = 'Wcn' 7 | let s:skip = 'textobj#ruby#skip()' 8 | 9 | function! textobj#ruby#skip() abort "{{{ 10 | return getline('.') =~ s:comment 11 | endfunction "}}} 12 | 13 | function! textobj#ruby#search(...) abort "{{{ 14 | let flag = get(a:000, 0, '') 15 | let middle = s:inner && g:textobj_ruby_inner_branch ? s:middle : '' 16 | return searchpair(s:start, middle, s:end, s:flags . flag, s:skip) 17 | endfunction "}} 18 | 19 | function! textobj#ruby#bounds() abort "{{{ 20 | call cursor(0, 2) 21 | let bottom = textobj#ruby#search() 22 | let top = textobj#ruby#search('b') 23 | if !(bottom && top) 24 | let [bottom, top] = [0, 0] 25 | endif 26 | call cursor(bottom, 0) 27 | return [top, bottom] 28 | endfunction "}}} 29 | 30 | " before performing the selection, check whether the mapping has been invoked 31 | " from visual mode (actually tests _not operator-pending_, because either vim 32 | " or textobj-user goes back to normal mode before arriving here). 33 | " if in visual, assume that the selection should grow to the next outer block. 34 | function! textobj#ruby#grow() abort "{{{ 35 | if g:textobj_ruby_grow && mode(1) != 'no' && getpos('''>') != getpos('''<') 36 | call cursor(line('''>') + 1 + s:inner, 2) 37 | endif 38 | endfunction "}}} 39 | 40 | " move up one line initially so the loop works for the first iteration 41 | function! textobj#ruby#block() abort "{{{ 42 | call textobj#ruby#grow() 43 | call cursor(line('.') - 1, 0) 44 | for i in range(v:count1) 45 | call cursor(line('.') + 1, 0) 46 | let result = textobj#ruby#bounds() 47 | endfor 48 | return result 49 | endfunction "}}} 50 | 51 | function! textobj#ruby#recursive(rex) abort "{{{ 52 | let bottom = -1 53 | let result = [0, 0] 54 | while bottom != 0 && bottom != line('$') 55 | let [top, bottom] = textobj#ruby#bounds() 56 | if getline(top) =~ a:rex 57 | let result = [top, bottom] 58 | break 59 | endif 60 | call cursor(bottom + 1, 2) 61 | endwhile 62 | return result 63 | endfunction "}}} 64 | 65 | function! textobj#ruby#func() abort "{{{ 66 | return textobj#ruby#recursive('\v^\s*def>') 67 | endfunction "}}} 68 | 69 | function! textobj#ruby#class() abort "{{{ 70 | return textobj#ruby#recursive('\v^\s*class>') 71 | endfunction "}}} 72 | 73 | function! textobj#ruby#saved_view(meth) abort "{{{ 74 | let saved_view = winsaveview() 75 | let result = call('textobj#ruby#' . a:meth, []) 76 | call winrestview(saved_view) 77 | return result 78 | endfunction "}}} 79 | 80 | function! s:pos(line) abort "{{{ 81 | return [0, a:line, 0, 0] 82 | endfunction "}}} 83 | 84 | function! textobj#ruby#a(meth) abort "{{{ 85 | let s:inner = 0 86 | let [top, bottom] = textobj#ruby#saved_view(a:meth) 87 | if g:textobj_ruby_inclusive && v:operator != 'c' 88 | while(top > 1 && getline(top - 1) =~ '^\s*$\|^\s*#') 89 | let top -= 1 90 | endwhile 91 | endif 92 | if top > 0 93 | return ['V', s:pos(top), s:pos(bottom)] 94 | endif 95 | endfunction "}}} 96 | 97 | function! textobj#ruby#i(meth) abort "{{{ 98 | let s:inner = 1 99 | let [top, bottom] = textobj#ruby#saved_view(a:meth) 100 | return ['V', s:pos(top + 1), s:pos(bottom - 1)] 101 | endfunction "}}} 102 | 103 | function! textobj#ruby#a_block() abort "{{{ 104 | return textobj#ruby#a('block') 105 | endfunction "}}} 106 | 107 | function! textobj#ruby#a_func() abort "{{{ 108 | return textobj#ruby#a('func') 109 | endfunction "}}} 110 | 111 | function! textobj#ruby#a_class() abort "{{{ 112 | return textobj#ruby#a('class') 113 | endfunction "}}} 114 | 115 | function! textobj#ruby#i_block() abort "{{{ 116 | return textobj#ruby#i('block') 117 | endfunction "}}} 118 | 119 | function! textobj#ruby#i_func() abort "{{{ 120 | return textobj#ruby#i('func') 121 | endfunction "}}} 122 | 123 | function! textobj#ruby#i_class() abort "{{{ 124 | return textobj#ruby#i('class') 125 | endfunction "}}} 126 | -------------------------------------------------------------------------------- /doc/textobj-ruby.txt: -------------------------------------------------------------------------------- 1 | *textobj-ruby.txt* text objects for ruby block, class function and name 2 | 3 | =============================================================================== 4 | Contents ~ 5 | 6 | 1. Introduction |textobj-ruby-introduction| 7 | 2. Description |textobj-ruby-description| 8 | 3. Usage |textobj-ruby-usage| 9 | 4. Customization |textobj-ruby-customization| 10 | 5. Details |textobj-ruby-details| 11 | 6. Dependencies |textobj-ruby-dependencies| 12 | 7. License |textobj-ruby-license| 13 | 8. References |textobj-ruby-references| 14 | 15 | =============================================================================== 16 | *textobj-ruby-introduction* 17 | Introduction ~ 18 | 19 | =============================================================================== 20 | *textobj-ruby-description* 21 | Description ~ 22 | 23 | This defines four ruby text objects – block, class, function and name. A block 24 | is delimited by the regex in the variable 'g:ruby_block_openers' and 'end'. A 25 | function is a special case of block, starting with 'def', class works the same 26 | way. The inner variant doesn't include the delimiter lines. 27 | 28 | A name is a module-scoped identifier like 'Module::Klass'. 29 | 30 | =============================================================================== 31 | *textobj-ruby-usage* 32 | Usage ~ 33 | 34 | By default, the omaps 'ir' and 'ar' are used for a block; 'if' and 'af' for a 35 | function, 'ic', 'ac' for a class and 'an' for a name. 36 | 37 | Specifying a count selects outer blocks recursively. 38 | 39 | =============================================================================== 40 | *textobj-ruby-customization* 41 | Customization ~ 42 | 43 | The variable 'g:textobj_ruby_inclusive' controls whether leading comments and a 44 | single blank line are included in the 'a' object if present, defaulting to 1. 45 | 46 | Consult the documentation of textobj-user [1] for custom mappings. 47 | 48 | If 'g:textobj_ruby_inner_branch' is '1', the inner block object will select the 49 | lines between 'else', 'elsif', 'rescue' and 'ensure' (configurable via 50 | 'g:ruby_block_middles') and the boundaries instead of including all conditional 51 | branches. 52 | 53 | =============================================================================== 54 | *textobj-ruby-details* 55 | Details ~ 56 | 57 | The plugin uses a consistent line based approach, with the lines containing the 58 | delimiters being part of the block. This means that invoking the text object on 59 | the word before a 'do' will select the block opened by it, which is especially 60 | useful on empty blocks. When the 'ir' object is used on the opener line, the 61 | selection will begin on the next line. 62 | 63 | The reason for inclusive mode is that when deleting or copying a function, you 64 | most likely want to paste it somewhere else and not leave an additional empty 65 | line around (given that you separate all functions by a single blank line). 66 | Comments directly above a block are most likely connected to it, so leaving 67 | them would be impractical. 68 | 69 | When invoked multiple times in visual mode, the selection grows to the next 70 | larger block, just like when invoking the operator with a count. To decide 71 | whether to grow, the visual selection is checked – if start and end do not 72 | match, it is assumed that the selection is already a block. This can result in 73 | faulty behaviour when invoked from visual mode with o nonzero selection and the 74 | cursor on the end delimiter line – the next outer block will be selected. 75 | 76 | =============================================================================== 77 | *textobj-ruby-dependencies* 78 | Dependencies ~ 79 | 80 | textobj-user [1] 81 | 82 | =============================================================================== 83 | *textobj-ruby-license* 84 | License ~ 85 | 86 | Copyright (c) Torsten Schmits. Distributed under the terms of the MIT License 87 | [2]. 88 | 89 | =============================================================================== 90 | *textobj-ruby-references* 91 | References ~ 92 | 93 | [1] https://github.com/kana/vim-textobj-user 94 | [2] http://opensource.org/licenses/MIT 95 | 96 | vim: ft=help 97 | --------------------------------------------------------------------------------