├── .travis.yml ├── README.md ├── autoload └── textobj │ └── anyblock.vim ├── plugin └── textobj │ └── anyblock.vim └── t ├── textobj_anyblock_spec.vim ├── travis.rb └── vspec_helper.rb /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | script: 3 | - vim --version 4 | - ./t/travis.rb 5 | install: 6 | - git clone https://github.com/kana/vim-vspec.git 7 | - git clone https://github.com/rhysd/vim-vspec-matchers.git 8 | - git clone https://github.com/kana/vim-textobj-user.git 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | One Text Object for Quotes, Parentheses and Braces 2 | ================================================== 3 | [![Build Status][]][Travis CI] 4 | 5 | This plugin provides text object mappings `ib` and `ab`. 6 | 7 | - `ib` is a union of `i(`, `i{`, `i[`, `i'`, `i"` and `i<`. 8 | - `ab` is a union of `a(`, `a{`, `a[`, `a'`, `a"` and `a<`. 9 | 10 | `ib` and `ab` match any of the text objects. If multiple text objects are matched, it selects one 11 | covering the most narrow region. For example, when the current line is `['abc', 'def']` and the 12 | cursor is at `e`, `vib` selects `def`. 13 | 14 | This plugin depends on [vim-textobj-user](https://github.com/kana/vim-textobj-user). 15 | Please install it in advance. 16 | 17 | If you want to change the blocks which `ib` and `ab` match, define `g:textobj#anyblock#blocks`. 18 | For example, if you install [vim-textobj-between](https://github.com/thinca/vim-textobj-between) and 19 | want to match `` `...` ``, set ``[ '(', '{', '[', '"', "'", '<' , 'f`']`` to it. 20 | 21 | If you want to define buffer local blocks, set them to `b:textobj_anyblock_buffer_local_blocks` as 22 | list of string on `FileType` autocmd event. 23 | 24 | ### License 25 | 26 | Distributed under [MIT License](http://opensource.org/licenses/MIT). 27 | 28 | ``` 29 | Copyright (c) 2013 rhysd 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 35 | of the Software, and to permit persons to whom the Software is furnished to do so, 36 | subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in all 39 | copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 42 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 43 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 44 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 45 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 46 | THE USE OR OTHER DEALINGS IN THE SOFTWARE. 47 | ``` 48 | 49 | [Build Status]: https://travis-ci.org/rhysd/vim-textobj-anyblock.png?branch=master 50 | [Travis CI]: https://travis-ci.org/rhysd/vim-textobj-anyblock 51 | -------------------------------------------------------------------------------- /autoload/textobj/anyblock.vim: -------------------------------------------------------------------------------- 1 | let s:save_cpo = &cpo 2 | set cpo&vim 3 | 4 | let g:textobj#anyblock#blocks = get(g:, 'textobj#anyblock#blocks', 5 | \ [ '(', '{', '[', '"', "'", '<', '`' ]) 6 | let g:textobj#anyblock#min_block_size = get(g:, 'textobj#anyblock#min_block_size', 2) 7 | let g:textobj#anyblock#inner_block_mapping = get(g:, 'textobj#anyblock#inner_block_mapping', 'i') 8 | let g:textobj#anyblock#outer_block_mapping = get(g:, 'textobj#anyblock#outer_block_mapping', 'a') 9 | let g:textobj#anyblock#no_remap_block_mapping = get(g:, 'textobj#anyblock#no_remap_block_mapping', 0) 10 | 11 | function! textobj#anyblock#select_i() abort 12 | return s:select(g:textobj#anyblock#inner_block_mapping) 13 | endfunction 14 | 15 | function! textobj#anyblock#select_a() abort 16 | return s:select(g:textobj#anyblock#outer_block_mapping) 17 | endfunction 18 | 19 | function! s:restore_screen_pos(before_screen_begin) abort 20 | let line_diff = line('w0') - a:before_screen_begin 21 | if line_diff > 0 22 | keepjumps execute 'normal!' line_diff."\" 23 | elseif line_diff < 0 24 | keepjumps execute 'normal!' (-line_diff)."\" 25 | endif 26 | endfunction 27 | 28 | function! s:select(chunk) abort 29 | let save_screen_begin = line('w0') 30 | let min_region = [getpos('.'), getpos('.')] 31 | let is_inner = a:chunk ==# g:textobj#anyblock#inner_block_mapping 32 | for block in get(b:, 'textobj_anyblock_local_blocks', []) + g:textobj#anyblock#blocks 33 | let r = s:get_region(a:chunk . block) 34 | if s:is_empty_region(r) || s:cursor_is_out_of_region(r, is_inner) 35 | continue 36 | endif 37 | 38 | let e = s:region_extent(r) 39 | if e < g:textobj#anyblock#min_block_size 40 | continue 41 | endif 42 | 43 | if !exists('l:min_region_extent') || min_region_extent > e 44 | let min_region_extent = e 45 | let min_region = r 46 | endif 47 | endfor 48 | call s:restore_screen_pos(save_screen_begin) 49 | if min_region[0] == min_region[1] 50 | return 0 51 | else 52 | return ['v', min_region[0], min_region[1]] 53 | endif 54 | endfunction 55 | 56 | function! s:region_extent(region) abort 57 | let extent = 0 58 | 59 | for line in range(a:region[0][1], a:region[1][1]) 60 | let line_width = strlen(getline(line)) 61 | let width = line_width 62 | 63 | if line == a:region[0][1] 64 | let width -= a:region[0][2] - 1 65 | endif 66 | 67 | if line == a:region[1][1] 68 | let width -= line_width - a:region[1][2] 69 | endif 70 | 71 | let extent += width 72 | endfor 73 | 74 | return extent 75 | endfunction 76 | 77 | function! s:get_region(textobj) abort 78 | let pos = getpos('.') 79 | normal! v 80 | 81 | let saved_vb = &vb 82 | let saved_t_vb = &t_vb 83 | 84 | " When 'i' is mapped to another mapping, :normal is not available (#13) 85 | if g:textobj#anyblock#no_remap_block_mapping 86 | let normal = 'normal!' 87 | else 88 | let normal = 'normal' 89 | endif 90 | 91 | try 92 | set vb t_vb= 93 | keepjumps execute 'silent' normal a:textobj 94 | keepjumps execute 'silent' 'normal!' "\" 95 | finally 96 | let &vb = saved_vb 97 | let &t_vb = saved_t_vb 98 | endtry 99 | 100 | call setpos('.', pos) 101 | return [getpos("'<"), getpos("'>")] 102 | endfunction 103 | 104 | function! s:is_empty_region(region) abort 105 | return a:region[1][1] < a:region[0][1] || (a:region[0][1] == a:region[1][1] && a:region[1][2] <= a:region[0][2]) 106 | endfunction 107 | 108 | 109 | function! s:cursor_is_out_of_region(region, is_inner) abort 110 | let [_, line, col, _] = getpos('.') 111 | let outside_surround_len = a:is_inner ? 1 : 0 112 | 113 | if line < a:region[0][1] || (line == a:region[0][1] && col < a:region[0][2] - outside_surround_len) 114 | return 1 115 | endif 116 | 117 | if line > a:region[1][1] || (line == a:region[1][1] && col > a:region[1][2] + outside_surround_len) 118 | return 1 119 | endif 120 | 121 | return 0 122 | endfunction 123 | 124 | let &cpo = s:save_cpo 125 | unlet s:save_cpo 126 | -------------------------------------------------------------------------------- /plugin/textobj/anyblock.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_textobj_anyblock') 2 | finish 3 | endif 4 | 5 | let s:save_cpo = &cpo 6 | set cpo&vim 7 | 8 | call textobj#user#plugin('anyblock', { 9 | \ '-' : { 10 | \ 'select-a' : 'ab', '*select-a-function*' : 'textobj#anyblock#select_a', 11 | \ 'select-i' : 'ib', '*select-i-function*' : 'textobj#anyblock#select_i', 12 | \ }, 13 | \ }) 14 | 15 | let &cpo = s:save_cpo 16 | unlet s:save_cpo 17 | 18 | let g:loaded_textobj_anyblock = 1 19 | -------------------------------------------------------------------------------- /t/textobj_anyblock_spec.vim: -------------------------------------------------------------------------------- 1 | let s:root_dir = matchstr(system('git rev-parse --show-cdup'), '[^\n]\+') 2 | execute 'set' 'rtp +=./'.s:root_dir 3 | 4 | set rtp +=~/.vim/bundle/vim-textobj-user 5 | set rtp +=~/.vim/bundle/vim-vspec-matchers 6 | 7 | runtime! plugin/textobj/anyblock.vim 8 | call vspec#matchers#load() 9 | 10 | describe 'Default settings' 11 | 12 | it 'provide default mappings' 13 | Expect '(textobj-anyblock-i)' to_be_mapped 14 | Expect '(textobj-anyblock-a)' to_be_mapped 15 | Expect 'ib' to_map_to '(textobj-anyblock-i)', 'xo' 16 | Expect 'ab' to_map_to '(textobj-anyblock-a)', 'xo' 17 | end 18 | 19 | it 'provide autoload functions' 20 | silent! call textobj#anyblock#select_i() 21 | Expect '*textobj#anyblock#select_i' to_exist 22 | Expect '*textobj#anyblock#select_a' to_exist 23 | end 24 | 25 | it 'provide variables to customize' 26 | Expect 'g:textobj#anyblock#blocks' to_exist_and_default_to [ '(', '{', '[', '"', "'", '<', '`' ] 27 | Expect 'g:textobj#anyblock#min_block_size' to_exist_and_default_to 2 28 | end 29 | end 30 | 31 | function! AddLine(str) 32 | put! =a:str 33 | endfunction 34 | 35 | describe '(textobj-anyblock-a)' 36 | 37 | before 38 | new 39 | call AddLine('(a{b"c''d[e]''"})') 40 | normal! gg0ff 41 | end 42 | 43 | after 44 | close! 45 | end 46 | 47 | it 'makes '''', "", <>, {} and () a object' 48 | normal dab 49 | Expect getline('.') ==# '(a{b"c''d[e]''"})' 50 | normal dab 51 | Expect getline('.') ==# '(a{b"c''d''"})' 52 | normal dab 53 | Expect getline('.') ==# '(a{b"c"})' 54 | normal dab 55 | Expect getline('.') ==# '(a{b})' 56 | normal dab 57 | Expect getline('.') ==# '(a)' 58 | normal dab 59 | Expect getline('.') ==# '' 60 | end 61 | end 62 | 63 | describe '(textobj-anyblock-i)' 64 | 65 | before 66 | new 67 | call AddLine('(a{b"c''d[e]''"})') 68 | normal! gg0ff 69 | let g:textobj#anyblock#min_block_size = 0 70 | end 71 | 72 | after 73 | close! 74 | let g:textobj#anyblock#min_block_size = 2 75 | end 76 | 77 | it 'makes '''', "", <>, {} and () inner object' 78 | normal dib 79 | Expect getline('.') ==# '(a{b"c''d[]''"})' 80 | normal dib 81 | Expect getline('.') ==# '(a{b"c''''"})' 82 | normal dib 83 | Expect getline('.') ==# '(a{b"c"})' 84 | normal dib 85 | Expect getline('.') ==# '(a{})' 86 | normal dib 87 | Expect getline('.') ==# '()' 88 | end 89 | end 90 | 91 | describe 'issues' 92 | before 93 | new 94 | end 95 | 96 | after 97 | close! 98 | end 99 | 100 | it 'considers length of surround at "i" object (#9)' 101 | call AddLine('(b(aaa)b)') 102 | normal! gg0f) 103 | normal dib 104 | Expect getline('.') ==# '(b()b)' 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /t/travis.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require './t/vspec_helper' 5 | 6 | v = Vspec.new(vspec_root: "./vim-vspec") 7 | v.run "t/textobj_anyblock_spec.vim", autoloads: ['./vim-vspec-matchers', './vim-textobj-user'] 8 | puts v.result 9 | exit 1 if ! v.success? || v.count_failed > 0 10 | -------------------------------------------------------------------------------- /t/vspec_helper.rb: -------------------------------------------------------------------------------- 1 | def which cmd 2 | dir = ENV['PATH'].split(':').find {|p| File.executable? File.join(p, cmd)} 3 | File.join(dir, cmd) unless dir.nil? 4 | end 5 | 6 | def locate query 7 | case RbConfig::CONFIG['host_os'] 8 | when /^darwin/ 9 | `mdfind #{query}` 10 | when /^linux/ 11 | `locate #{query}` 12 | else 13 | raise "unknown environment" 14 | end 15 | end 16 | 17 | class Vspec 18 | 19 | private 20 | 21 | def detect_from_rtp 22 | @@detect_from_rtp_cache ||= `vim -u ~/.vimrc -e -s -c 'set rtp' -c q` 23 | .match(/^\s+runtimepath=(.+)\n$/)[0] 24 | .split(',') 25 | .find{|p| p =~ /vim-vspec$/ } 26 | end 27 | 28 | def detect_from_locate 29 | @@detect_from_locate_cache ||= locate('vim-vspec').split("\n").first 30 | end 31 | 32 | def has_vspec? dir 33 | File.executable?(File.expand_path(File.join dir, 'bin', 'vspec')) 34 | end 35 | 36 | def detect_vspec_root 37 | case 38 | when has_vspec?(detect_from_rtp) 39 | detect_from_rtp 40 | when has_vspec?(detect_from_locate) 41 | detect_from_locate 42 | when which('vspec') 43 | File.dirname(File.dirname(which('vspec'))) 44 | when has_vspec?("#{ENV['HOME']}/.vim") 45 | "#{ENV['HOME']}/.vim" 46 | when has_vspec?("vim-vspec") 47 | "./vim-vspec" 48 | else 49 | raise "vspec is not found" 50 | end 51 | end 52 | 53 | public 54 | 55 | def initialize( path: File.executable?("/usr/local/bin/vim") ? "/usr/local/bin" : "", 56 | vspec_root: detect_vspec_root ) 57 | @path = path 58 | @vspec_root = vspec_root 59 | @vspec = File.join vspec_root, "bin", "vspec" 60 | end 61 | 62 | def run(file, autoloads: []) 63 | @result = `PATH=#{@path}:$PATH #{@vspec} #{@vspec_root} #{autoloads.join ' '} #{file}` 64 | @success = @result.scan(/^Error detected while processing function/).empty? 65 | end 66 | 67 | def count_failed 68 | @result.scan(/^not ok /).size if @success && @result 69 | end 70 | 71 | def all_passed? 72 | count_failed == 0 if @success && @result 73 | end 74 | 75 | def success? 76 | @success 77 | end 78 | 79 | attr_reader :result 80 | 81 | end 82 | 83 | --------------------------------------------------------------------------------