├── README.md ├── autoload └── textobj │ └── elixir.vim ├── plugin └── textobj │ └── elixir.vim └── samples └── test1.ex /README.md: -------------------------------------------------------------------------------- 1 | # Vim text objects for elixir block structures 2 | 3 | This Vim plugin makes text objects with various elixir block structures. 4 | Many end-terminated blocks are parsed using regex, indentation and syntax 5 | highlight. This is more correct than parsing text with regex only. 6 | 7 | ## Simple one operator-pending mapping `e` 8 | 9 | Elixir text objects include: 'setup_all', 'setup', 'describe', 'test', 10 | 'unless', 'quote', 'case', 'cond', 'when', 'with', 'for', 'if', 11 | 'defprotocol', 'defmodule', 'defmacro', 'defmacrop', 'defimpl', 'defp', 12 | 'def'. 13 | 14 | Example: 15 | 16 | `#\%` is the place of your cursor. 17 | 18 | ```elixir 19 | def hoge(yo) do 20 | if yo do 21 | IO.puts "yo!" 22 | #\% 23 | end 24 | IO.puts "everyone!" 25 | end 26 | ``` 27 | 28 | Typing `dae` removes whole `if` block 29 | 30 | ```elixir 31 | def hoge(yo) do 32 | #\% 33 | IO.puts "everyone!" 34 | end 35 | ``` 36 | 37 | or `die` removes innner `if` block. 38 | 39 | ```elixir 40 | def hoge(yo) do 41 | if yo do 42 | #\% 43 | end 44 | end 45 | ``` 46 | 47 | When a cursor places at line 6, 48 | 49 | ```elixir 50 | def hoge(yo) do 51 | if yo do 52 | IO.puts "yo!" 53 | end 54 | IO.puts "everyone!" #\% 55 | end 56 | ``` 57 | 58 | type `die` removes inner `def` block. 59 | 60 | ```elixir 61 | def hoge(yo) do 62 | end 63 | ``` 64 | 65 | This plugin requires [vim-textobj-user](https://github.com/kana/vim-textobj-user) 66 | 67 | -------------------------------------------------------------------------------- /autoload/textobj/elixir.vim: -------------------------------------------------------------------------------- 1 | let s:save_cpo = &cpo 2 | set cpo&vim 3 | 4 | " each pair has the syntax element and the vim syntax label 5 | let s:terms=[ 6 | \ ['setup_all', 'elixirExUnitMacro'], 7 | \ ['setup', 'elixirExUnitMacro'], 8 | \ ['describe', 'elixirExUnitMacro'], 9 | \ ['test', 'elixirExUnitMacro'], 10 | \ ['unless', 'elixirKeyword'], 11 | \ ['quote', 'elixirKeyword'], 12 | \ ['case', 'elixirKeyword'], 13 | \ ['cond', 'elixirKeyword'], 14 | \ ['when', 'elixirKeyword'], 15 | \ ['with', 'elixirKeyword'], 16 | \ ['for', 'elixirKeyword'], 17 | \ ['if', 'elixirKeyword'], 18 | \ ['defprotocol', 'elixirProtocolDefine'], 19 | \ ['defmodule', 'elixirModuleDefine'], 20 | \ ['defmacro', 'elixirMacroDefine'], 21 | \ ['defmacrop', 'elixirPrivateMacroDefine'], 22 | \ ['defimpl', 'elixirImplDefine'], 23 | \ ['defp', 'elixirPrivateDefine'], 24 | \ ['def', 'elixirDefine'] 25 | \] 26 | 27 | " ----------------------------------------------------- 28 | " -- TERM - query / transform term 29 | 30 | function! s:term_reg() 31 | return join(s:term_keys(), '\|') 32 | endfunction 33 | 34 | function! s:term_dict() 35 | let acc = {} 36 | for pair in s:terms 37 | let acc[pair[0]] = pair[1] 38 | endfor 39 | return acc 40 | endfunction 41 | 42 | function! s:term_keys() 43 | let acc = [] 44 | for pair in s:terms 45 | call add(acc, pair[0]) 46 | endfor 47 | return acc 48 | endfunction 49 | 50 | function! s:syntax_for_term(term) 51 | return s:term_dict()[a:term] 52 | endfunction 53 | 54 | " ----------------------------------------------------- 55 | " -- INDENT - calculate the line indent 56 | 57 | function! s:find_indent(direction) 58 | let pos = getpos('.') 59 | let line = search('[a-zA-A0-9]', a:direction ) 60 | call setpos('.', pos) 61 | if line == 0 62 | throw 'not found' 63 | endif 64 | return indent(line) 65 | endfunction 66 | 67 | function! s:prev_indent() 68 | return s:find_indent('bW') + 2 69 | endfunction 70 | 71 | function! s:next_indent() 72 | return s:find_indent('W') + 2 73 | endfunction 74 | 75 | function! s:calculate_indent() 76 | return max([s:prev_indent(), s:next_indent()]) 77 | endfunction 78 | 79 | function! s:current_indent() 80 | let line = substitute(getline('.'),' ','', 'g') 81 | return empty(line) ? s:calculate_indent() : indent('.') 82 | endfunction 83 | 84 | " ----------------------------------------------------- 85 | " -- SEARCH - search for a text object 86 | 87 | function! s:syntax_for_line(line) 88 | return synIDattr(synID(a:line, col('.'),1), 'name') 89 | endfunction 90 | 91 | function! s:search_head(block, indent) 92 | while 1 93 | let line = search( '\<\%('.a:block.'\)\>', 'bW' ) 94 | if line == 0 95 | throw 'not found' 96 | endif 97 | let syntax = s:syntax_for_term(expand('')) 98 | if syntax == '' 99 | throw 'not found' 100 | endif 101 | let current_indent = indent('.') 102 | if current_indent < a:indent && syntax ==# s:syntax_for_line(line) 103 | return [syntax, current_indent, getpos('.')] 104 | endif 105 | endwhile 106 | endfunction 107 | 108 | function! s:search_tail(head_indent) 109 | while 1 110 | let line = search('end', 'W') 111 | if line == 0 112 | throw 'not found' 113 | endif 114 | if indent('.') == a:head_indent && 'elixirBlockDefinition' ==# s:syntax_for_line(line) 115 | return getpos('.') 116 | endif 117 | endwhile 118 | endfunction 119 | 120 | function! s:search_block(block) 121 | let pos = getpos('.') 122 | try 123 | let indent = s:current_indent() 124 | let [syntax, head_indent, head] = s:search_head(a:block, indent) 125 | call setpos('.', pos) 126 | let tail = s:search_tail(head_indent) 127 | return ['V', head, tail] 128 | catch /^not found$/ 129 | echohl Error | echo 'block is not found.' | echohl None 130 | call setpos('.', pos) 131 | return 0 132 | endtry 133 | endfunction 134 | 135 | " narrow range by 1 line on both sides 136 | function! s:inside(range) 137 | " check if range exists 138 | if type(a:range) != type([]) || a:range[1][1]+1 > a:range[2][1]-1 139 | return 0 140 | endif 141 | let range = a:range 142 | let range[1][1] += 1 143 | let range[2][1] -= 1 144 | return range 145 | endfunction 146 | 147 | " ----------------------------------------------------- 148 | " -- SELECT - main functions 149 | 150 | function! textobj#elixir#any_select_i() 151 | return s:inside(s:search_block(s:term_reg())) 152 | endfunction 153 | 154 | function! textobj#elixir#any_select_a() 155 | return s:search_block(s:term_reg()) 156 | endfunction 157 | 158 | let &cpo = s:save_cpo 159 | unlet s:save_cpo 160 | -------------------------------------------------------------------------------- /plugin/textobj/elixir.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_textobj_elixir_plugin') 2 | finish 3 | endif 4 | let g:loaded_textobj_elixir_plugin = 1 5 | 6 | let s:save_cpo = &cpo 7 | set cpo&vim 8 | 9 | call textobj#user#plugin('elixir', { 10 | \ 'any' : { 11 | \ 'select-a' : 'ae', '*select-a-function*' : 'textobj#elixir#any_select_a', 12 | \ 'select-i' : 'ie', '*select-i-function*' : 'textobj#elixir#any_select_i', 13 | \ }, 14 | \ }) 15 | 16 | let &cpo = s:save_cpo 17 | unlet s:save_cpo 18 | -------------------------------------------------------------------------------- /samples/test1.ex: -------------------------------------------------------------------------------- 1 | defmodule Test1 do 2 | 3 | def normal_function do 4 | x = 1 5 | end 6 | 7 | def normal_function_with_blank do 8 | x = 1 9 | 10 | y = 1 11 | end 12 | 13 | defp private_function do 14 | x = 1 15 | end 16 | 17 | def empty_function do 18 | 19 | end 20 | 21 | test "empty test" do 22 | 23 | end 24 | 25 | describe "empty description" do 26 | 27 | end 28 | 29 | end 30 | --------------------------------------------------------------------------------