├── test ├── .themisrc └── textobj-functioncall.vim ├── .travis.yml ├── ftplugin ├── ruby │ └── textobj_functioncall.vim ├── vim │ └── textobj_functioncall.vim └── julia │ └── textobj_functioncall.vim ├── appveyor.yml ├── README.md ├── plugin └── textobj │ └── functioncall.vim ├── doc ├── textobj-functioncall.jax └── textobj-functioncall.txt └── autoload └── textobj └── functioncall.vim /test/.themisrc: -------------------------------------------------------------------------------- 1 | set encoding=utf-8 2 | execute 'set runtimepath+=' . expand(':p:h:h') 3 | runtime! :p:h:h/plugin/*.vim 4 | set noswapfile 5 | let g:assert = themis#helper('assert') 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | env: 4 | - PPA=yes 5 | 6 | install: 7 | - if [ x"$PPA" == "xyes" ] ; then sudo add-apt-repository ppa:pi-rho/dev -y; fi 8 | - sudo apt-get update -q 9 | - sudo apt-get install vim-nox 10 | - git clone https://github.com/thinca/vim-themis 11 | 12 | before_script: 13 | - vim --version 14 | 15 | script: 16 | - ./vim-themis/bin/themis 17 | -------------------------------------------------------------------------------- /ftplugin/ruby/textobj_functioncall.vim: -------------------------------------------------------------------------------- 1 | if exists("b:did_ftplugin_textobj_functioncall") 2 | finish 3 | endif 4 | let b:did_ftplugin_textobj_functioncall = 1 5 | 6 | let s:save_cpo = &cpo 7 | set cpo-=C 8 | 9 | let b:textobj_functioncall_patterns = [ 10 | \ { 11 | \ 'header' : '\<\h\k*[!?]\?', 12 | \ 'bra' : '(', 13 | \ 'ket' : ')', 14 | \ 'footer' : '', 15 | \ }, 16 | \ { 17 | \ 'header' : '\<\h\k*', 18 | \ 'bra' : '\[', 19 | \ 'ket' : '\]', 20 | \ 'footer' : '', 21 | \ }, 22 | \ ] 23 | 24 | let &cpo = s:save_cpo 25 | unlet s:save_cpo 26 | -------------------------------------------------------------------------------- /ftplugin/vim/textobj_functioncall.vim: -------------------------------------------------------------------------------- 1 | if exists("b:did_ftplugin_textobj_functioncall") 2 | finish 3 | endif 4 | let b:did_ftplugin_textobj_functioncall = 1 5 | 6 | let s:save_cpo = &cpo 7 | set cpo-=C 8 | 9 | let b:textobj_functioncall_patterns = [ 10 | \ { 11 | \ 'header' : '\C\<\%(\h\|[sa]:\h\|g:[A-Z]\)\k*', 12 | \ 'bra' : '(', 13 | \ 'ket' : ')', 14 | \ 'footer' : '', 15 | \ }, 16 | \ { 17 | \ 'header' : '\%(^\|[^:]\)\zs\<\%([abglstvw]:\)\?\h\k*', 18 | \ 'bra' : '\[', 19 | \ 'ket' : '\]', 20 | \ 'footer' : '', 21 | \ }, 22 | \ ] 23 | 24 | let &cpo = s:save_cpo 25 | unlet s:save_cpo 26 | -------------------------------------------------------------------------------- /ftplugin/julia/textobj_functioncall.vim: -------------------------------------------------------------------------------- 1 | if exists("b:did_ftplugin_textobj_functioncall") 2 | finish 3 | endif 4 | let b:did_ftplugin_textobj_functioncall = 1 5 | 6 | let s:save_cpo = &cpo 7 | set cpo-=C 8 | 9 | " To include multibyte characters 10 | let b:textobj_functioncall_patterns = [ 11 | \ { 12 | \ 'header' : '\%#=2\<[[:upper:][:lower:]_]\k*!\?', 13 | \ 'bra' : '(', 14 | \ 'ket' : ')', 15 | \ 'footer' : '', 16 | \ }, 17 | \ { 18 | \ 'header' : '\%#=2\<[[:upper:][:lower:]_]\k*', 19 | \ 'bra' : '\[', 20 | \ 'ket' : '\]', 21 | \ 'footer' : '', 22 | \ }, 23 | \ ] 24 | 25 | let &cpo = s:save_cpo 26 | unlet s:save_cpo 27 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | clone_depth: 1 3 | build: off 4 | test_script: 5 | - ps: >- 6 | git clone --quiet --depth 1 https://github.com/thinca/vim-themis.git themis 7 | 8 | 9 | $zip_vim74 = $Env:APPVEYOR_BUILD_FOLDER + '\vim74.zip' 10 | 11 | $vim74 = $Env:APPVEYOR_BUILD_FOLDER + '\vim74' 12 | 13 | $zip_vim73 = $Env:APPVEYOR_BUILD_FOLDER + '\vim73.zip' 14 | 15 | $vim73 = $Env:APPVEYOR_BUILD_FOLDER + '\vim73' 16 | 17 | (New-Object Net.WebClient).DownloadFile('http://files.kaoriya.net/vim/vim74-kaoriya-win64.zip', $zip_vim74) 18 | 19 | (New-Object Net.WebClient).DownloadFile('http://files.kaoriya.net/vim/vim73-kaoriya-win64.zip', $zip_vim73) 20 | 21 | 22 | [Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') > $null 23 | 24 | [System.IO.Compression.ZipFile]::ExtractToDirectory($zip_vim74, $vim74) 25 | 26 | [System.IO.Compression.ZipFile]::ExtractToDirectory($zip_vim73, $vim73) 27 | 28 | 29 | $Env:THEMIS_VIM = $vim74 + '\vim74-kaoriya-win64\vim.exe' 30 | 31 | & $Env:THEMIS_VIM --version 32 | 33 | .\themis\bin\themis.bat 34 | 35 | if ($?) { 36 | $Env:THEMIS_VIM = $vim73 + '\vim73-kaoriya-win64\vim.exe' 37 | 38 | & $Env:THEMIS_VIM --version 39 | 40 | .\themis\bin\themis.bat 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vim-textobj-functioncall 2 | ======================== 3 | 4 | [![Build status](https://ci.appveyor.com/api/projects/status/7fk1871r0a8bmb5h?svg=true)](https://ci.appveyor.com/project/machakann/vim-textobj-functioncall) 5 | [![Build Status](https://travis-ci.org/machakann/vim-textobj-functioncall.svg?branch=master)](https://travis-ci.org/machakann/vim-textobj-functioncall) 6 | 7 | The vim textobject plugin to treat function-call regions. 8 | 9 | Default mappings are assigned to `if` and `af`. 10 | 11 | * `if` and `af`, both of them select a region like `func(argument)`. 12 | * `if` and `af` behave differently when a function takes another function as its argument. 13 | * `if` selects the most inner function under the cursor. 14 | * `af` selects the first function including the cursor position by its parenthesis. However if any candidate would be found, it falls back to `if`. 15 | 16 | 17 | ```vim 18 | # : cursor position 19 | call func1(func2(argument)) 20 | 21 | |<-----if---->| 22 | call func1(func2(argument)) 23 | |<--------af-------->| 24 | ``` 25 | 26 | --- 27 | 28 | If you don't like to map to `if` and `af`, please define the variable named `g:textobj_functioncall_no_default_key_mappings` in your vimrc. 29 | ```vim 30 | let g:textobj_functioncall_no_default_key_mappings = 1 31 | ``` 32 | 33 | And then map to your preferable keys. 34 | ```vim 35 | xmap iF (textobj-functioncall-i) 36 | omap iF (textobj-functioncall-i) 37 | xmap aF (textobj-functioncall-a) 38 | omap aF (textobj-functioncall-a) 39 | ``` 40 | 41 | --- 42 | 43 | This textobject could select following patterns. 44 | * `func(argument)` 45 | * `array[index]` 46 | 47 | --- 48 | -------------------------------------------------------------------------------- /plugin/textobj/functioncall.vim: -------------------------------------------------------------------------------- 1 | " Vim global plugin to define text-object for function call. 2 | " Last Change: 20-Mar-2017. 3 | " Maintainer : Masaaki Nakamura 4 | 5 | " License : NYSL 6 | " Japanese 7 | " English (Unofficial) 8 | 9 | if exists("g:loaded_textobj_functioncall") 10 | finish 11 | endif 12 | let g:loaded_textobj_functioncall = 1 13 | 14 | onoremap (textobj-functioncall-i) :call textobj#functioncall#i('o') 15 | xnoremap (textobj-functioncall-i) :call textobj#functioncall#i('x') 16 | onoremap (textobj-functioncall-a) :call textobj#functioncall#a('o') 17 | xnoremap (textobj-functioncall-a) :call textobj#functioncall#a('x') 18 | onoremap (textobj-functioncall-innerparen-i) :call textobj#functioncall#ip('o') 19 | xnoremap (textobj-functioncall-innerparen-i) :call textobj#functioncall#ip('x') 20 | onoremap (textobj-functioncall-innerparen-a) :call textobj#functioncall#ap('o') 21 | xnoremap (textobj-functioncall-innerparen-a) :call textobj#functioncall#ap('x') 22 | 23 | """ default keymappings 24 | " If g:textobj_delimited_no_default_key_mappings has been defined, then quit immediately. 25 | if exists('g:textobj_functioncall_no_default_key_mappings') | finish | endif 26 | 27 | if !hasmapto('(textobj-functioncall-i)') 28 | silent! omap if (textobj-functioncall-i) 29 | silent! xmap if (textobj-functioncall-i) 30 | endif 31 | 32 | if !hasmapto('(textobj-functioncall-a)') 33 | silent! omap af (textobj-functioncall-a) 34 | silent! xmap af (textobj-functioncall-a) 35 | endif 36 | -------------------------------------------------------------------------------- /doc/textobj-functioncall.jax: -------------------------------------------------------------------------------- 1 | *textobj-functioncall.txt* 関数を呼んでるっぽい箇所を選択する。 2 | Last change:20-Aug-2021. 3 | 4 | 書いた人 : machakann 5 | ライセンス : NYSL license 6 | Japanese 7 | English (Unofficial) 8 | 9 | 必須要件: 10 | - Vim 7.2 or later 11 | 12 | ============================================================================== 13 | CONTENTS *textobj-functioncall-contents* 14 | 15 | INTRODUCTION |textobj-functioncall-introduction| 16 | KEYMAPPINGS |textobj-functioncall-keymappings| 17 | CONFIGURATION |textobj-functioncall-configuration| 18 | 19 | ============================================================================== 20 | INTRODUCTION *textobj-functioncall-introduction* 21 | 22 | *textobj-functioncall* はソースコード中の関数呼び出しっぽい部分を選択するため 23 | のテキストオブジェクトプラグインです。言うまでもないことですが、関数は多くの言 24 | 語において重要な構成要素となっています。このプラグインはこの関数呼び出し部分を 25 | 編集するうえで強力な手段を提供します。例えば次のような行について考えてみましょ 26 | う。 27 | > 28 | call func(argument) 29 | < 30 | カーソルが "func(argument)" の中のどこかにある場合、このプラグインの提供する 31 | キーマッピングはそれ全体を対象に選択します。さらに必要であれば "argument" 部分 32 | のみを選択するキーマッピングも設定可能です。 33 | 34 | まず二つの主要なキーマッピング |(textobj-functioncall-i)| 及び 35 | |(textobj-functioncall-a)| はどちらも "func(argument)" のような関数呼び 36 | 出し全体を選択します。ただし、関数がその引数として別の関数を取る場合に挙動が少 37 | し異なります。 38 | |(textobj-functioncall-i)| は常にカーソルが関数呼び出し部分のどこかにあ 39 | るものと考え、そのうち最も内側の部分を選択します。なので下の例では 40 | "func2(argument)" を選択するでしょう。 41 | > 42 | # : cursor 43 | call func1(func2(argument)) 44 | |<----------->| : selected region 45 | < 46 | |(textobj-functioncall-a)| は関数呼び出しの括弧の中のどこかにカーソルが 47 | あるものとして考えます。なのでカーソル位置を包含する最初の関数呼び出し部分を探 48 | して選択します。先の例であれば、 "func1(func2(argument))" を選択することになり 49 | ます。 50 | > 51 | # : cursor 52 | call func1(func2(argument)) 53 | |<------------------>| : selected region 54 | < 55 | ただし、もしこの方法で探して対象部分が見つからなければ 56 | |(textobj-functioncall-i)| の方法へ回帰します。 57 | 58 | ------------------------------------------------------------------------------ 59 | 必要であれば |(textobj-functioncall-innerparen-i)| および、 60 | |(textobj-functioncall-innerparen-a)| もご使用になれます。これらは先述の 61 | 主要なキーマッピングと同じように対象を探しますが、括弧内の引数部分のみを選択し 62 | ます。 63 | > 64 | # : cursor 65 | call func1(func2(argument)) 66 | |<---->| :(textobj-functioncall-innerparen-i) 67 | |<----------->| :(textobj-functioncall-innerparen-a) 68 | < 69 | 70 | ------------------------------------------------------------------------------ 71 | 実のところ、これらのキーマッピングは便利のために次のような似たパターンにも反応 72 | します。 73 | > 74 | array[index] 75 | < 76 | もしこれらの挙動を変更したい場合、 |textobj-functioncall-configuration| をご参 77 | 照ください。 78 | ============================================================================== 79 | KEYMAPPINGS *textobj-functioncall-keymappings* 80 | 81 | このプラグインは次に掲げるキーマッピングを提供します。 82 | 83 | 種別 キーマッピング デフォルト設定 84 | -------------------------------------------------------------------------- 85 | i |(textobj-functioncall-i)| if 86 | a |(textobj-functioncall-a)| af 87 | innerparen-i |(textobj-functioncall-innerparen-i)| 88 | innerparen-a |(textobj-functioncall-innerparen-a)| 89 | -------------------------------------------------------------------------- 90 | 91 | もし、デフォルト設定が必要なければあなたの vimrc で変数 92 | |g:textobj_functioncall_no_default_key_mappings| を定義しておいてください。 93 | > 94 | let g:textobj_functioncall_no_default_key_mappings = 1 95 | < 96 | こうすることでデフォルトのマッピングは設定されません。お好きなキーへ設定しなお 97 | してください。 98 | > 99 | xmap if (textobj-functioncall-innerparen-i) 100 | omap if (textobj-functioncall-innerparen-i) 101 | xmap af (textobj-functioncall-innerparen-a) 102 | omap af (textobj-functioncall-innerparen-a) 103 | < 104 | 105 | ------------------------------------------------------------------------------ 106 | keymappings~ 107 | (textobj-functioncall-i) *(textobj-functioncall-i)* 108 | 関数呼び出し様の部分を選択します。カーソル下の最も内側の対象を選択しま 109 | す。 110 | 111 | (textobj-functioncall-a) *(textobj-functioncall-a)* 112 | 関数呼び出し様の部分を選択します。カーソルを括弧により包含する最初の対 113 | 象を選択します。ただし該当がない場合は 114 | |(textobj-functioncall-i)| へ回帰します。 115 | 116 | *(textobj-functioncall-innerparen-i)* 117 | (textobj-functioncall-innerparen-i) 118 | 関数呼び出し様の部分の括弧内のみを選択します。対象は 119 | |(textobj-functioncall-i)| と同様の方法により検索します。 120 | 121 | *(textobj-functioncall-innerparen-a)* 122 | (textobj-functioncall-innerparen-a) 123 | 関数呼び出し様の部分の括弧内のみを選択します。対象は 124 | |(textobj-functioncall-a)| と同様の方法により検索します。 125 | 126 | ------------------------------------------------------------------------------ 127 | キーマッピング関数~ 128 | 129 | 以下の関数は新しい(独立した)キーマッピングを定義するのに使えます。 130 | 131 | *textobjtextobj#functioncall#i()* 132 | textobjtextobj#functioncall#i({mode} [, {patternlist}]) 133 | 134 | *textobjtextobj#functioncall#a()* 135 | textobjtextobj#functioncall#a({mode} [, {patternlist}]) 136 | 137 | *textobjtextobj#functioncall#ip()* 138 | textobjtextobj#functioncall#ip({mode} [, {patternlist}]) 139 | 140 | *textobjtextobj#functioncall#ap()* 141 | textobjtextobj#functioncall#ap({mode} [, {patternlist}]) 142 | 143 | これらの関数はテキストオブジェクトのキーマッピングを定義するのに使われ 144 | ています。例えば、デフォルトのキーマッピング 145 | |(textobj-functioncall-i)| は |textobjtextobj#functioncall#i()| 146 | を使って定義されています。 147 | > 148 | onoremap (textobj-functioncall-i) 149 | \ :call textobj#functioncall#i('o') 150 | 151 | xnoremap (textobj-functioncall-i) 152 | \ :call textobj#functioncall#i('x') 153 | < 154 | 同様に |(textobj-functioncall-innerparen-i)| は 155 | |textobjtextobj#functioncall#ip()|を使って定義されています。 156 | > 157 | onoremap (textobj-functioncall-innerparen-i) 158 | \ :call textobj#functioncall#ip('o') 159 | xnoremap (textobj-functioncall-innerparen-i) 160 | \ :call textobj#functioncall#ip('x') 161 | < 162 | 省略可能な引数の {patternlist} が与えられた場合は、そのキーマッピング 163 | は |g:textobj_functioncall_patterns| を無視して {patternlist} を代わり 164 | に使います。以下の例は `${foo}` のような文字列を選択するための新しいテ 165 | キストオブジェクト `i$`, `a$` を定義する方法を示しています。これらのキ 166 | ーマッピングは |g:textobj_functioncall_patterns| の変更を必要とせず、 167 | デフォルトのキーマッピングと干渉しません。 168 | > 169 | let g:patternlist = [ 170 | \ { 171 | \ 'header': '\$' 172 | \ 'bra': '{' 173 | \ 'ket': '}' 174 | \ 'footer': '' 175 | \ } 176 | \ ] 177 | 178 | onoremap i$ 179 | \ :call textobj#functioncall#i('o', g:patternlist) 180 | xnoremap i$ 181 | \ :call textobj#functioncall#i('x', g:patternlist) 182 | 183 | onoremap a$ 184 | \ :call textobj#functioncall#a('o', g:patternlist) 185 | xnoremap a$ 186 | \ :call textobj#functioncall#a('x', g:patternlist) 187 | < 188 | ============================================================================== 189 | CONFIGURATION *textobj-functioncall-configuration* 190 | 191 | Valiables~ 192 | *g:textobj_functioncall_no_default_key_mappings* 193 | g:textobj_functioncall_no_default_key_mappings 194 | この変数を vimrc で定義するとデフォルトのマッピングが提供されません。 195 | 196 | g:textobj_functioncall_search_lines *g:textobj_functioncall_search_lines* 197 | カーソル位置より何行ほど対象の検索範囲に含めるかを指定します。正の数を 198 | 指定するとカーソル位置よりその行数を検索範囲とします。ゼロおよび負数を 199 | 指定すると常にファイルの先頭あるいは末尾までを検索範囲としますが、大き 200 | なファイルで動作が遅くなるかもしれません。デフォルト値は30です。 201 | 202 | g:textobj_functioncall_patterns *g:textobj_functioncall_patterns* 203 | 関数呼び出しにマッチするルールのリストです。一つのルールは一つの辞書か 204 | らなり、四つのキー (`header`, `bra`, `ket`, `footer`) をもっています。 205 | それぞれの値は正規表現パターンであり、`header` は関数名に、`bra` と 206 | `ket` は両括弧に、`footer` は閉じ括弧に続く文字列にマッチします。 207 | > 208 | let g:textobj_functioncall_patterns = [ 209 | \ { 210 | \ 'header' : '\<\h\k*', 211 | \ 'bra' : '(', 212 | \ 'ket' : ')', 213 | \ 'footer' : '', 214 | \ }, 215 | \ { 216 | \ 'header' : '\<\h\k*', 217 | \ 'bra' : '\[', 218 | \ 'ket' : '\]', 219 | \ 'footer' : '', 220 | \ }, 221 | \ ] 222 | < 223 | |g:textobj_functioncall_patterns| が定義されていない場合、 224 | `g:textobj_functioncall_default_patterns` が使われます。 225 | `b:textobj_functioncall_patterns` がバッファに定義されている場合、これ 226 | が優先して使われます。 227 | 228 | ============================================================================== 229 | vim:tw=78:ts=8:ft=help:norl:noet: 230 | -------------------------------------------------------------------------------- /doc/textobj-functioncall.txt: -------------------------------------------------------------------------------- 1 | *textobj-functioncall.txt* Select a region that calls function. 2 | Last change:20-Aug-2021. 3 | 4 | Author : machakann 5 | License : NYSL license 6 | Japanese 7 | English (Unofficial) 8 | 9 | Requirements: 10 | - Vim 7.2 or later 11 | 12 | ============================================================================== 13 | CONTENTS *textobj-functioncall-contents* 14 | 15 | INTRODUCTION |textobj-functioncall-introduction| 16 | KEYMAPPINGS |textobj-functioncall-keymappings| 17 | CONFIGURATION |textobj-functioncall-configuration| 18 | 19 | ============================================================================== 20 | INTRODUCTION *textobj-functioncall-introduction* 21 | 22 | *textobj-functioncall* is a Vim plugin to implement textobjects to select a 23 | region that calls function in your source code. Needless to say, Function is a 24 | essential element for many programming languages. This plugin gives powerful 25 | ways to edit them. For example, think about a line like: 26 | > 27 | call func(argument) 28 | < 29 | If the cursor is on the string "func(argument)", the keymappings which is 30 | given by this plugin select it. Additionally optional keymappings could gives 31 | a way to select only argument in the same situation. 32 | 33 | There are two main keymappings, |(textobj-functioncall-i)| and 34 | |(textobj-functioncall-a)|. Both of them select a region like 35 | "func(argument)", but they search for the region in different way. The 36 | difference would appear when a function takes another function as its 37 | argument. 38 | |(textobj-functioncall-i)| considers that the cursor is on somewhere in 39 | the region looking for. Thus it selects the most inner part of matched 40 | regions, "func2(argument)" for the following example. 41 | > 42 | # : cursor 43 | call func1(func2(argument)) 44 | |<----------->| : selected region 45 | < 46 | |(textobj-functioncall-a)| considers that the cursor is in between 47 | parenthesis of the region looking for. Thus it selects the first region to 48 | include the position of the cursor, so it searches somewhat outer region like 49 | "func1(func2(argument))" for the example. 50 | > 51 | # : cursor 52 | call func1(func2(argument)) 53 | |<------------------>| : selected region 54 | < 55 | If any candidate is found, it falls back to the function of 56 | |(textobj-functioncall-i)|. 57 | 58 | ------------------------------------------------------------------------------ 59 | This plugin also serves optional keymappings, 60 | |(textobj-functioncall-innerparen-i)| and 61 | |(textobj-functioncall-innerparen-a)|. They behaves similar as the above 62 | two. But they selects only inside the parenthesis. 63 | > 64 | # : cursor 65 | call func1(func2(argument)) 66 | |<---->| :(textobj-functioncall-innerparen-i) 67 | |<----------->| :(textobj-functioncall-innerparen-a) 68 | < 69 | 70 | ------------------------------------------------------------------------------ 71 | In fact, these keymappings response the similar expressions also in default. 72 | > 73 | array[index] 74 | < 75 | If you want to change the behavior, please see 76 | |textobj-functioncall-configuration|. 77 | 78 | ============================================================================== 79 | KEYMAPPINGS *textobj-functioncall-keymappings* 80 | 81 | This plugin serves following keymappings. 82 | 83 | kind keymappings default keymappings 84 | -------------------------------------------------------------------------- 85 | i |(textobj-functioncall-i)| if 86 | a |(textobj-functioncall-a)| af 87 | innerparen-i |(textobj-functioncall-innerparen-i)| 88 | innerparen-a |(textobj-functioncall-innerparen-a)| 89 | -------------------------------------------------------------------------- 90 | 91 | If you do not need default keymappings, define a variable named 92 | |g:textobj_functioncall_no_default_key_mappings| in your vimrc. 93 | > 94 | let g:textobj_functioncall_no_default_key_mappings = 1 95 | < 96 | Then default mappings are never applied. And map them again as you like. 97 | > 98 | xmap if (textobj-functioncall-innerparen-i) 99 | omap if (textobj-functioncall-innerparen-i) 100 | xmap af (textobj-functioncall-innerparen-a) 101 | omap af (textobj-functioncall-innerparen-a) 102 | < 103 | 104 | ------------------------------------------------------------------------------ 105 | keymappings~ 106 | (textobj-functioncall-i) *(textobj-functioncall-i)* 107 | This keymapping selects a function-call-like region. It searches the 108 | most inner region under the cursor. 109 | 110 | (textobj-functioncall-a) *(textobj-functioncall-a)* 111 | This keymapping selects a function-call-like region. It searches the 112 | function-call-like region which include the cursor by its parenthesis. 113 | If any candidate is found, it falls back to the function of 114 | |(textobj-functioncall-i)|. 115 | 116 | *(textobj-functioncall-innerparen-i)* 117 | (textobj-functioncall-innerparen-i) 118 | This keymapping selects a argument part of function-call-like region. 119 | It searches in the same way with |(textobj-functioncall-i)|. 120 | 121 | *(textobj-functioncall-innerparen-a)* 122 | (textobj-functioncall-innerparen-a) 123 | This keymapping selects a argument part of function-call-like region. 124 | It searches in the same way with |(textobj-functioncall-a)|. 125 | 126 | ------------------------------------------------------------------------------ 127 | keymapping functions~ 128 | 129 | The functions listed below enable to define new (independent) keymappings. 130 | 131 | *textobjtextobj#functioncall#i()* 132 | textobjtextobj#functioncall#i({mode} [, {patternlist}]) 133 | 134 | *textobjtextobj#functioncall#a()* 135 | textobjtextobj#functioncall#a({mode} [, {patternlist}]) 136 | 137 | *textobjtextobj#functioncall#ip()* 138 | textobjtextobj#functioncall#ip({mode} [, {patternlist}]) 139 | 140 | *textobjtextobj#functioncall#ap()* 141 | textobjtextobj#functioncall#ap({mode} [, {patternlist}]) 142 | 143 | These functions are employed to define a textobject keymapping. 144 | For instance, the default keymapping |(textobj-functioncall-i)| 145 | is defined by |textobjtextobj#functioncall#i()|. 146 | > 147 | onoremap (textobj-functioncall-i) 148 | \ :call textobj#functioncall#i('o') 149 | 150 | xnoremap (textobj-functioncall-i) 151 | \ :call textobj#functioncall#i('x') 152 | < 153 | Similarly, |(textobj-functioncall-innerparen-i)| is defined 154 | by |textobjtextobj#functioncall#ip()|. 155 | > 156 | onoremap (textobj-functioncall-innerparen-i) 157 | \ :call textobj#functioncall#ip('o') 158 | xnoremap (textobj-functioncall-innerparen-i) 159 | \ :call textobj#functioncall#ip('x') 160 | < 161 | The optional argument {patternlist} is useful when user wants to make 162 | a new keymapping which ignores |g:textobj_functioncall_patterns|. 163 | The following example shows how to make new keymappings `i$` and `a$` 164 | for selecting texts like `${foo}` without interfering with 165 | |g:textobj_functioncall_patterns| and the default keymappings. 166 | > 167 | let g:patternlist = [ 168 | \ { 169 | \ 'header': '\$' 170 | \ 'bra': '{' 171 | \ 'ket': '}' 172 | \ 'footer': '' 173 | \ } 174 | \ ] 175 | 176 | onoremap i$ 177 | \ :call textobj#functioncall#i('o', g:patternlist) 178 | xnoremap i$ 179 | \ :call textobj#functioncall#i('x', g:patternlist) 180 | 181 | onoremap a$ 182 | \ :call textobj#functioncall#a('o', g:patternlist) 183 | xnoremap a$ 184 | \ :call textobj#functioncall#a('x', g:patternlist) 185 | < 186 | 187 | ============================================================================== 188 | CONFIGURATION *textobj-functioncall-configuration* 189 | 190 | Valiables~ 191 | *g:textobj_functioncall_no_default_key_mappings* 192 | g:textobj_functioncall_no_default_key_mappings 193 | If you define this valiable in your vimrc, default keymappings would 194 | not be defined. 195 | 196 | g:textobj_functioncall_search_lines *g:textobj_functioncall_search_lines* 197 | The number of lines to search the function-call-like region. If a 198 | positive number is assigned, the textobject searches the candidate 199 | in the range before and ahead the number of lines from the current 200 | line. If zero or a negative number is assigned, the textobject 201 | searches the whole part of the current buffer. But it might be slow 202 | when you edit a large file. The default value is 30. 203 | 204 | g:textobj_functioncall_patterns *g:textobj_functioncall_patterns* 205 | This is a list of patterns matched to a function-call-like region. 206 | A rule is a dictionary which have four keys; header, bra, ket and 207 | footer. All the values are regex patterns. The header pattern should 208 | match with the function name, bra and ket match with parentheses, and 209 | the footer pattern matches with something after closing parenthesis. 210 | > 211 | let g:textobj_functioncall_patterns = [ 212 | \ { 213 | \ 'header' : '\<\h\k*', 214 | \ 'bra' : '(', 215 | \ 'ket' : ')', 216 | \ 'footer' : '', 217 | \ }, 218 | \ { 219 | \ 'header' : '\<\h\k*', 220 | \ 'bra' : '\[', 221 | \ 'ket' : '\]', 222 | \ 'footer' : '', 223 | \ }, 224 | \ ] 225 | < 226 | If |g:textobj_functioncall_patterns| is not defined, 227 | `g:textobj_functioncall_default_patterns` is used instead. 228 | If `b:textobj_functioncall_patterns` is defined for the buffer, 229 | it is prior to the others. 230 | 231 | ============================================================================== 232 | vim:tw=78:ts=8:ft=help:norl:noet: 233 | -------------------------------------------------------------------------------- /autoload/textobj/functioncall.vim: -------------------------------------------------------------------------------- 1 | " textobj-functioncall 2 | 3 | let s:save_cpo = &cpoptions 4 | set cpoptions&vim 5 | 6 | let s:FALSE = 0 7 | let s:TRUE = 1 8 | let s:type_list = type([]) 9 | let s:type_dict = type({}) 10 | let s:null_pos = [0, 0] 11 | 12 | " default patterns 13 | unlet! g:textobj_functioncall_default_patterns 14 | let g:textobj_functioncall_default_patterns = [ 15 | \ { 16 | \ 'header' : '\<\h\k*', 17 | \ 'bra' : '(', 18 | \ 'ket' : ')', 19 | \ 'footer' : '', 20 | \ }, 21 | \ { 22 | \ 'header' : '\<\h\k*', 23 | \ 'bra' : '\[', 24 | \ 'ket' : '\]', 25 | \ 'footer' : '', 26 | \ }, 27 | \ ] 28 | lockvar! g:textobj_functioncall_default_patterns 29 | 30 | 31 | function! textobj#functioncall#i(mode, ...) abort 32 | call call("s:prototype", ['i', a:mode] + a:000) 33 | endfunction 34 | function! textobj#functioncall#a(mode, ...) abort 35 | call call("s:prototype", ['a', a:mode] + a:000) 36 | endfunction 37 | function! textobj#functioncall#ip(mode, ...) abort 38 | call call("s:prototype", ['ip', a:mode] + a:000) 39 | endfunction 40 | function! textobj#functioncall#ap(mode, ...) abort 41 | call call("s:prototype", ['ap', a:mode] + a:000) 42 | endfunction 43 | 44 | 45 | function! s:prototype(kind, mode, ...) abort 46 | let l:count = v:count1 47 | if a:0 48 | let pattern_list = a:1 49 | else 50 | let pattern_list = s:resolve_patterns() 51 | endif 52 | let searchlines = s:get('textobj_sandwich_function_searchlines' , 30) 53 | let stopline = {} 54 | if searchlines < 0 55 | let stopline.upper = 1 56 | let stopline.lower = line('$') 57 | else 58 | let stopline.upper = max([1, line('.') - searchlines]) 59 | let stopline.lower = min([line('.') + searchlines, line('$')]) 60 | endif 61 | 62 | let [start, end] = [s:null_pos, s:null_pos] 63 | let view = winsaveview() 64 | try 65 | let candidates = s:gather_candidates(a:kind, a:mode, l:count, pattern_list, stopline) 66 | let elected = s:elect(candidates, l:count) 67 | if elected != {} 68 | let [start, end] = s:to_range(a:kind, elected) 69 | endif 70 | finally 71 | call winrestview(view) 72 | endtry 73 | call s:select(start, end) 74 | endfunction 75 | 76 | 77 | function! s:Candidate(head, bra, ket, tail, pat, rank) abort 78 | return { 79 | \ 'head': a:head, 80 | \ 'bra': a:bra, 81 | \ 'ket': a:ket, 82 | \ 'tail': a:tail, 83 | \ 'rank': a:rank, 84 | \ 'pattern': a:pat, 85 | \ 'len': s:buflen(a:head, a:tail), 86 | \ } 87 | endfunction 88 | 89 | 90 | function! s:gather_candidates(kind, mode, count, pattern_list, stopline) abort 91 | let curpos = getpos('.')[1:2] 92 | let rank = 0 93 | let candidates = [] 94 | for pattern in a:pattern_list 95 | let rank += 1 96 | let candidates += s:search_pattern(pattern, a:kind, a:mode, a:count, rank, curpos, a:stopline) 97 | call cursor(curpos) 98 | endfor 99 | return a:kind[0] is# 'a' && candidates == [] 100 | \ ? s:gather_candidates('i', a:mode, a:count, a:pattern_list, a:stopline) 101 | \ : candidates 102 | endfunction 103 | 104 | 105 | function! s:search_pattern(pat, kind, mode, count, rank, curpos, stopline) abort 106 | let a:pat.head = a:pat.header . a:pat.bra 107 | let a:pat.tail = a:pat.ket . a:pat.footer 108 | 109 | let brapos = s:search_key_bra(a:kind, a:curpos, a:pat, a:stopline) 110 | if brapos == s:null_pos | return [] | endif 111 | let is_string = s:is_string_syntax(brapos) 112 | 113 | let candidates = [] 114 | while len(candidates) < a:count 115 | let c = s:get_candidate(a:pat, a:kind, a:mode, a:rank, brapos, is_string, a:stopline) 116 | if c != {} 117 | call add(candidates, c) 118 | endif 119 | call cursor(brapos) 120 | 121 | " move to the next 'bra' 122 | let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'b', '', a:stopline.upper) 123 | if brapos == s:null_pos | break | endif 124 | let is_string = s:is_string_syntax(brapos) 125 | endwhile 126 | return candidates 127 | endfunction 128 | 129 | 130 | function! s:search_key_bra(kind, curpos, pat, stopline) abort 131 | let brapos = s:null_pos 132 | if a:kind[0] is# 'a' 133 | " search for the first 'bra' 134 | if searchpos(a:pat.tail, 'cn', a:stopline.lower) == a:curpos 135 | let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'b', '', a:stopline.upper) 136 | endif 137 | let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'b', '', a:stopline.upper) 138 | elseif a:kind[0] is# 'i' 139 | let head_start = searchpos(a:pat.head, 'bc', a:stopline.upper) 140 | let head_end = searchpos(a:pat.head, 'ce', a:stopline.lower) 141 | call cursor(a:curpos) 142 | let tail_start = searchpos(a:pat.tail, 'bc', a:stopline.upper) 143 | let tail_end = searchpos(a:pat.tail, 'ce', a:stopline.lower) 144 | 145 | " check the initial position 146 | if s:is_in_between(a:curpos, head_start, head_end) 147 | " cursor is on a header 148 | call cursor(head_end) 149 | elseif s:is_in_between(a:curpos, tail_start, tail_end) 150 | " cursor is on a footer 151 | call cursor(tail_start) 152 | if tail_start[1] == 1 153 | normal! k$ 154 | else 155 | normal! h 156 | endif 157 | else 158 | " cursor is in between a bra and a ket 159 | call cursor(a:curpos) 160 | endif 161 | 162 | " move to the corresponded 'bra' 163 | let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'bc', '', a:stopline.upper) 164 | endif 165 | return brapos 166 | endfunction 167 | 168 | 169 | function! s:get_candidate(pat, kind, mode, rank, brapos, is_string, stopline) abort 170 | " 'bra' should accompany with 'header' 171 | let headstart = searchpos(a:pat.head, 'bc', a:stopline.upper) 172 | let headend = searchpos(a:pat.head, 'ce', a:stopline.lower) 173 | call cursor(a:brapos) 174 | if !s:is_in_between(a:brapos, headstart, headend) 175 | return {} 176 | endif 177 | let headpos = headstart 178 | 179 | " search for the paired 'ket' 180 | let skip = 's:is_string_syntax(getpos(".")[1:2]) != a:is_string' 181 | let ketpos = searchpairpos(a:pat.bra, '', a:pat.ket, '', skip, a:stopline.lower) 182 | if ketpos == s:null_pos 183 | return {} 184 | endif 185 | let tailpos = searchpos(a:pat.tail, 'ce', a:stopline.lower) 186 | 187 | if searchpos(a:pat.tail, 'bcn', a:stopline.upper) != ketpos 188 | return {} 189 | endif 190 | 191 | let c = s:Candidate(headpos, a:brapos, ketpos, tailpos, a:pat, a:rank) 192 | if !s:is_valid_candidate(c, a:kind, a:mode, a:is_string) 193 | return {} 194 | endif 195 | return c 196 | endfunction 197 | 198 | 199 | function! s:elect(candidates, count) abort 200 | if a:candidates == [] 201 | return {} 202 | endif 203 | let filter = 'v:val.head != s:null_pos && v:val.bra != s:null_pos && v:val.ket != s:null_pos && v:val.tail != s:null_pos' 204 | call filter(a:candidates, filter) 205 | call sort(a:candidates, 's:compare') 206 | if len(a:candidates) < a:count 207 | return {} 208 | endif 209 | return a:candidates[a:count - 1] 210 | endfunction 211 | 212 | 213 | function! s:to_range(kind, candidate) abort 214 | if a:kind[1] is# 'p' 215 | let [start, end] = s:parameter_region(a:candidate) 216 | else 217 | let start = a:candidate.head 218 | let end = a:candidate.tail 219 | endif 220 | return [start, end] 221 | endfunction 222 | 223 | 224 | function! s:select(start, end) abort 225 | if a:start == s:null_pos || a:end == s:null_pos 226 | return 227 | endif 228 | normal! v 229 | call cursor(a:start) 230 | normal! o 231 | call cursor(a:end) 232 | if &selection is# 'exclusive' 233 | normal! l 234 | endif 235 | endfunction 236 | 237 | 238 | function! s:is_in_between(pos, start, end) abort 239 | return (a:pos != s:null_pos) && (a:start != s:null_pos) && (a:end != s:null_pos) 240 | \ && ((a:pos[0] > a:start[0]) || ((a:pos[0] == a:start[0]) && (a:pos[1] >= a:start[1]))) 241 | \ && ((a:pos[0] < a:end[0]) || ((a:pos[0] == a:end[0]) && (a:pos[1] <= a:end[1]))) 242 | endfunction 243 | 244 | 245 | function! s:is_string_syntax(pos) abort 246 | return match(map(synstack(a:pos[0], a:pos[1]), 'synIDattr(synIDtrans(v:val), "name")'), 'String') > -1 247 | endfunction 248 | 249 | 250 | function! s:is_continuous_syntax(brapos, ketpos) abort 251 | let start_col = a:brapos[1] 252 | for lnum in range(a:brapos[0], a:ketpos[0]) 253 | if lnum == a:ketpos[0] 254 | let end_col= a:ketpos[1] 255 | else 256 | let end_col= col([lnum, '$']) 257 | endif 258 | for col in range(start_col, end_col) 259 | if match(map(synstack(lnum, col), 'synIDattr(synIDtrans(v:val), "name")'), 'String') < 0 260 | return 0 261 | endif 262 | endfor 263 | let start_col = 1 264 | endfor 265 | return 1 266 | endfunction 267 | 268 | 269 | function! s:is_valid_syntax(candidate, is_string) abort 270 | return !a:is_string || s:is_continuous_syntax(a:candidate.bra, a:candidate.ket) 271 | endfunction 272 | 273 | 274 | function! s:is_same_or_adjacent(p1, p2) abort 275 | return a:p1 == a:p2 || (a:p1[0] == a:p2[0] && a:p1[1]+1 == a:p2[1]) 276 | endfunction 277 | 278 | 279 | function! s:is_wider(candidate, start, end) abort 280 | return (s:is_ahead(a:start, a:candidate.head) && s:is_same_or_ahead(a:candidate.tail, a:end)) || 281 | \ (s:is_same_or_ahead(a:start, a:candidate.head) && s:is_ahead(a:candidate.tail, a:end)) 282 | endfunction 283 | 284 | 285 | function! s:is_ahead(p1, p2) abort 286 | return (a:p1[0] > a:p2[0]) || (a:p1[0] == a:p2[0] && a:p1[1] > a:p2[1]) 287 | endfunction 288 | 289 | 290 | function! s:is_same_or_ahead(p1, p2) abort 291 | return (a:p1[0] > a:p2[0]) || (a:p1[0] == a:p2[0] && a:p1[1] >= a:p2[1]) 292 | endfunction 293 | 294 | 295 | function! s:is_valid_candidate(c, kind, mode, is_string) abort 296 | if a:kind[1] is# 'p' && s:is_same_or_adjacent(a:c.bra, a:c.ket) 297 | return s:FALSE 298 | endif 299 | if a:mode is# 'x' && !s:is_wider(a:c, getpos("'<")[1:2], getpos("'>")[1:2]) 300 | return s:FALSE 301 | endif 302 | if !s:is_valid_syntax(a:c, a:is_string) 303 | return s:FALSE 304 | endif 305 | return s:TRUE 306 | endfunction 307 | 308 | 309 | function! s:resolve_patterns() abort 310 | let patterns = 311 | \ get(b:, 'textobj_functioncall_patterns', 312 | \ get(g:, 'textobj_functioncall_patterns', 313 | \ g:textobj_functioncall_default_patterns)) 314 | unlockvar! patterns 315 | return patterns 316 | endfunction 317 | 318 | 319 | function! s:parameter_region(candidate) abort 320 | let whichwrap = &whichwrap 321 | let &whichwrap = 'h,l' 322 | let [visualhead, visualtail] = [getpos("'<"), getpos("'>")] 323 | try 324 | normal! v 325 | call cursor(a:candidate.bra) 326 | call search(a:candidate.pattern.bra, 'ce', a:candidate.ket[0]) 327 | normal! l 328 | let head = getpos('.')[1:2] 329 | normal! o 330 | call cursor(a:candidate.ket) 331 | normal! h 332 | let tail = getpos('.')[1:2] 333 | execute "normal! \" 334 | finally 335 | let &whichwrap = whichwrap 336 | call setpos("'<", visualhead) 337 | call setpos("'>", visualtail) 338 | endtry 339 | return [head, tail] 340 | endfunction 341 | 342 | 343 | function! s:buflen(start, end) abort 344 | " start, end -> [lnum, col] 345 | if a:start[0] == a:end[0] 346 | let len = a:end[1] - a:start[1] + 1 347 | else 348 | let len = (line2byte(a:end[0]) + a:end[1]) - (line2byte(a:start[0]) + a:start[1]) + 1 349 | endif 350 | return len 351 | endfunction 352 | 353 | 354 | function! s:compare(i1, i2) abort 355 | " i1, i2 -> Candidate 356 | if a:i1.len < a:i2.len 357 | return -1 358 | elseif a:i1.len > a:i2.len 359 | return 1 360 | else 361 | return a:i2.rank - a:i1.rank 362 | endif 363 | endfunction 364 | 365 | 366 | function! s:get(name, default) abort 367 | return exists('b:' . a:name) ? b:[a:name] 368 | \ : exists('g:' . a:name) ? g:[a:name] 369 | \ : a:default 370 | endfunction 371 | 372 | 373 | let &cpoptions = s:save_cpo 374 | unlet s:save_cpo 375 | 376 | " vim:set foldmethod=marker: 377 | " vim:set commentstring="%s: 378 | -------------------------------------------------------------------------------- /test/textobj-functioncall.vim: -------------------------------------------------------------------------------- 1 | scriptencoding utf-8 2 | let s:suite = themis#suite('textobj-functioncall') 3 | 4 | function! s:suite.before_each() abort "{{{ 5 | %delete 6 | syntax clear 7 | set filetype= 8 | unlet! g:textobj_functioncall_patterns b:textobj_functioncall_patterns 9 | endfunction 10 | "}}} 11 | function! s:suite.after() abort "{{{ 12 | call s:suite.before_each() 13 | endfunction 14 | "}}} 15 | 16 | function! s:suite.basic() dict abort "{{{ 17 | let testset = [ 18 | \ { 19 | \ 'buffer': ['call header(foo)'], 20 | \ 'input': '5ly%s', 21 | \ 'expect': { 22 | \ "\(textobj-functioncall-i)": 'header(foo)', 23 | \ "\(textobj-functioncall-a)": 'header(foo)', 24 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 25 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 26 | \ }, 27 | \ }, 28 | \ 29 | \ { 30 | \ 'buffer': ['call header(foo)'], 31 | \ 'input': '7ly%s', 32 | \ 'expect': { 33 | \ "\(textobj-functioncall-i)": 'header(foo)', 34 | \ "\(textobj-functioncall-a)": 'header(foo)', 35 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 36 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 37 | \ }, 38 | \ }, 39 | \ 40 | \ { 41 | \ 'buffer': ['call header(foo)'], 42 | \ 'input': '10ly%s', 43 | \ 'expect': { 44 | \ "\(textobj-functioncall-i)": 'header(foo)', 45 | \ "\(textobj-functioncall-a)": 'header(foo)', 46 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 47 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 48 | \ }, 49 | \ }, 50 | \ 51 | \ { 52 | \ 'buffer': ['call header(foo)'], 53 | \ 'input': '11ly%s', 54 | \ 'expect': { 55 | \ "\(textobj-functioncall-i)": 'header(foo)', 56 | \ "\(textobj-functioncall-a)": 'header(foo)', 57 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 58 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 59 | \ }, 60 | \ }, 61 | \ 62 | \ { 63 | \ 'buffer': ['call header(foo)'], 64 | \ 'input': '13ly%s', 65 | \ 'expect': { 66 | \ "\(textobj-functioncall-i)": 'header(foo)', 67 | \ "\(textobj-functioncall-a)": 'header(foo)', 68 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 69 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 70 | \ }, 71 | \ }, 72 | \ 73 | \ { 74 | \ 'buffer': ['call header(foo)'], 75 | \ 'input': '15ly%s', 76 | \ 'expect': { 77 | \ "\(textobj-functioncall-i)": 'header(foo)', 78 | \ "\(textobj-functioncall-a)": 'header(foo)', 79 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 80 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 81 | \ }, 82 | \ }, 83 | \ ] 84 | call s:trytestset(testset) 85 | 86 | let g:textobj_functioncall_patterns = [{'header': '', 'bra': '(', 'ket': ')', 'footer': 'footer'}] 87 | let testset = [ 88 | \ { 89 | \ 'buffer': ['call (foo)footer'], 90 | \ 'input': '5ly%s', 91 | \ 'expect': { 92 | \ "\(textobj-functioncall-i)": '(foo)footer', 93 | \ "\(textobj-functioncall-a)": '(foo)footer', 94 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 95 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 96 | \ }, 97 | \ }, 98 | \ 99 | \ { 100 | \ 'buffer': ['call (foo)footer'], 101 | \ 'input': '7ly%s', 102 | \ 'expect': { 103 | \ "\(textobj-functioncall-i)": '(foo)footer', 104 | \ "\(textobj-functioncall-a)": '(foo)footer', 105 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 106 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 107 | \ }, 108 | \ }, 109 | \ 110 | \ { 111 | \ 'buffer': ['call (foo)footer'], 112 | \ 'input': '9ly%s', 113 | \ 'expect': { 114 | \ "\(textobj-functioncall-i)": '(foo)footer', 115 | \ "\(textobj-functioncall-a)": '(foo)footer', 116 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 117 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 118 | \ }, 119 | \ }, 120 | \ 121 | \ { 122 | \ 'buffer': ['call (foo)footer'], 123 | \ 'input': '10ly%s', 124 | \ 'expect': { 125 | \ "\(textobj-functioncall-i)": '(foo)footer', 126 | \ "\(textobj-functioncall-a)": '(foo)footer', 127 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 128 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 129 | \ }, 130 | \ }, 131 | \ 132 | \ { 133 | \ 'buffer': ['call (foo)footer'], 134 | \ 'input': '12ly%s', 135 | \ 'expect': { 136 | \ "\(textobj-functioncall-i)": '(foo)footer', 137 | \ "\(textobj-functioncall-a)": '(foo)footer', 138 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 139 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 140 | \ }, 141 | \ }, 142 | \ 143 | \ { 144 | \ 'buffer': ['call (foo)footer'], 145 | \ 'input': '15ly%s', 146 | \ 'expect': { 147 | \ "\(textobj-functioncall-i)": '(foo)footer', 148 | \ "\(textobj-functioncall-a)": '(foo)footer', 149 | \ "\(textobj-functioncall-innerparen-i)": 'foo', 150 | \ "\(textobj-functioncall-innerparen-a)": 'foo', 151 | \ }, 152 | \ }, 153 | \ ] 154 | call s:trytestset(testset) 155 | endfunction 156 | "}}} 157 | function! s:suite.cursorpos() dict abort "{{{ 158 | let testset = [ 159 | \ { 160 | \ 'buffer': ['call foo(foo, bar(bar))'], 161 | \ 'input': '13ly%s', 162 | \ 'expect': { 163 | \ "\(textobj-functioncall-i)": 'foo(foo, bar(bar))', 164 | \ "\(textobj-functioncall-a)": 'foo(foo, bar(bar))', 165 | \ "\(textobj-functioncall-innerparen-i)": 'foo, bar(bar)', 166 | \ "\(textobj-functioncall-innerparen-a)": 'foo, bar(bar)', 167 | \ }, 168 | \ }, 169 | \ 170 | \ { 171 | \ 'buffer': ['call foo(foo, bar(bar))'], 172 | \ 'input': '14ly%s', 173 | \ 'expect': { 174 | \ "\(textobj-functioncall-i)": 'bar(bar)', 175 | \ "\(textobj-functioncall-a)": 'foo(foo, bar(bar))', 176 | \ "\(textobj-functioncall-innerparen-i)": 'bar', 177 | \ "\(textobj-functioncall-innerparen-a)": 'foo, bar(bar)', 178 | \ }, 179 | \ }, 180 | \ 181 | \ { 182 | \ 'buffer': ['call foo(foo, bar(bar))'], 183 | \ 'input': '17ly%s', 184 | \ 'expect': { 185 | \ "\(textobj-functioncall-i)": 'bar(bar)', 186 | \ "\(textobj-functioncall-a)": 'foo(foo, bar(bar))', 187 | \ "\(textobj-functioncall-innerparen-i)": 'bar', 188 | \ "\(textobj-functioncall-innerparen-a)": 'foo, bar(bar)', 189 | \ }, 190 | \ }, 191 | \ 192 | \ { 193 | \ 'buffer': ['call foo(foo, bar(bar))'], 194 | \ 'input': '21ly%s', 195 | \ 'expect': { 196 | \ "\(textobj-functioncall-i)": 'bar(bar)', 197 | \ "\(textobj-functioncall-a)": 'foo(foo, bar(bar))', 198 | \ "\(textobj-functioncall-innerparen-i)": 'bar', 199 | \ "\(textobj-functioncall-innerparen-a)": 'foo, bar(bar)', 200 | \ }, 201 | \ }, 202 | \ 203 | \ { 204 | \ 'buffer': ['call foo(foo, bar(bar))'], 205 | \ 'input': '22ly%s', 206 | \ 'expect': { 207 | \ "\(textobj-functioncall-i)": 'foo(foo, bar(bar))', 208 | \ "\(textobj-functioncall-a)": 'foo(foo, bar(bar))', 209 | \ "\(textobj-functioncall-innerparen-i)": 'foo, bar(bar)', 210 | \ "\(textobj-functioncall-innerparen-a)": 'foo, bar(bar)', 211 | \ }, 212 | \ }, 213 | \ ] 214 | call s:trytestset(testset) 215 | endfunction 216 | "}}} 217 | function! s:suite.count() dict abort "{{{ 218 | " count 219 | let testset = [ 220 | \ { 221 | \ 'buffer': ['call foo(foo, bar(bar, baz(baz)))'], 222 | \ 'input': '28ly1%s', 223 | \ 'expect': { 224 | \ "\(textobj-functioncall-i)": 'baz(baz)', 225 | \ "\(textobj-functioncall-a)": 'baz(baz)', 226 | \ "\(textobj-functioncall-innerparen-i)": 'baz', 227 | \ "\(textobj-functioncall-innerparen-a)": 'baz', 228 | \ }, 229 | \ }, 230 | \ 231 | \ { 232 | \ 'buffer': ['call foo(foo, bar(bar, baz(baz)))'], 233 | \ 'input': '28ly2%s', 234 | \ 'expect': { 235 | \ "\(textobj-functioncall-i)": 'bar(bar, baz(baz))', 236 | \ "\(textobj-functioncall-a)": 'bar(bar, baz(baz))', 237 | \ "\(textobj-functioncall-innerparen-i)": 'bar, baz(baz)', 238 | \ "\(textobj-functioncall-innerparen-a)": 'bar, baz(baz)', 239 | \ }, 240 | \ }, 241 | \ 242 | \ { 243 | \ 'buffer': ['call foo(foo, bar(bar, baz(baz)))'], 244 | \ 'input': '28ly3%s', 245 | \ 'expect': { 246 | \ "\(textobj-functioncall-i)": 'foo(foo, bar(bar, baz(baz)))', 247 | \ "\(textobj-functioncall-a)": 'foo(foo, bar(bar, baz(baz)))', 248 | \ "\(textobj-functioncall-innerparen-i)": 'foo, bar(bar, baz(baz))', 249 | \ "\(textobj-functioncall-innerparen-a)": 'foo, bar(bar, baz(baz))', 250 | \ }, 251 | \ }, 252 | \ 253 | \ { 254 | \ 'buffer': ['call foo(foo, bar(bar, baz(baz)))'], 255 | \ 'input': '28ly4%s', 256 | \ 'expect': { 257 | \ "\(textobj-functioncall-i)": '', 258 | \ "\(textobj-functioncall-a)": '', 259 | \ "\(textobj-functioncall-innerparen-i)": '', 260 | \ "\(textobj-functioncall-innerparen-a)": '', 261 | \ }, 262 | \ }, 263 | \ ] 264 | call s:trytestset(testset) 265 | endfunction 266 | "}}} 267 | function! s:suite.multiplepatterns() dict abort "{{{ 268 | let testset = [ 269 | \ { 270 | \ 'buffer': ['call foo(bar[baz])'], 271 | \ 'input': '5ly%s', 272 | \ 'expect': { 273 | \ "\(textobj-functioncall-i)": 'foo(bar[baz])', 274 | \ "\(textobj-functioncall-a)": 'foo(bar[baz])', 275 | \ "\(textobj-functioncall-innerparen-i)": 'bar[baz]', 276 | \ "\(textobj-functioncall-innerparen-a)": 'bar[baz]', 277 | \ }, 278 | \ }, 279 | \ 280 | \ { 281 | \ 'buffer': ['call foo(bar[baz])'], 282 | \ 'input': '10ly%s', 283 | \ 'expect': { 284 | \ "\(textobj-functioncall-i)": 'bar[baz]', 285 | \ "\(textobj-functioncall-a)": 'foo(bar[baz])', 286 | \ "\(textobj-functioncall-innerparen-i)": 'baz', 287 | \ "\(textobj-functioncall-innerparen-a)": 'bar[baz]', 288 | \ }, 289 | \ }, 290 | \ 291 | \ { 292 | \ 'buffer': ['call foo(bar[baz])'], 293 | \ 'input': '14ly%s', 294 | \ 'expect': { 295 | \ "\(textobj-functioncall-i)": 'bar[baz]', 296 | \ "\(textobj-functioncall-a)": 'bar[baz]', 297 | \ "\(textobj-functioncall-innerparen-i)": 'baz', 298 | \ "\(textobj-functioncall-innerparen-a)": 'baz', 299 | \ }, 300 | \ }, 301 | \ ] 302 | call s:trytestset(testset) 303 | endfunction 304 | "}}} 305 | function! s:suite.filetype() dict abort "{{{ 306 | let testset = [ 307 | \ { 308 | \ 'buffer': ['call s:foo(a:bar[baz])'], 309 | \ 'input': '5ly%s', 310 | \ 'expect': { 311 | \ "\(textobj-functioncall-i)": 's:foo(a:bar[baz])', 312 | \ "\(textobj-functioncall-a)": 's:foo(a:bar[baz])', 313 | \ "\(textobj-functioncall-innerparen-i)": 'a:bar[baz]', 314 | \ "\(textobj-functioncall-innerparen-a)": 'a:bar[baz]', 315 | \ }, 316 | \ }, 317 | \ 318 | \ { 319 | \ 'buffer': ['call s:foo(a:bar[baz])'], 320 | \ 'input': '11ly%s', 321 | \ 'expect': { 322 | \ "\(textobj-functioncall-i)": 'a:bar[baz]', 323 | \ "\(textobj-functioncall-a)": 's:foo(a:bar[baz])', 324 | \ "\(textobj-functioncall-innerparen-i)": 'baz', 325 | \ "\(textobj-functioncall-innerparen-a)": 'a:bar[baz]', 326 | \ }, 327 | \ }, 328 | \ 329 | \ { 330 | \ 'buffer': ['call s:foo(a:bar[baz])'], 331 | \ 'input': '18ly%s', 332 | \ 'expect': { 333 | \ "\(textobj-functioncall-i)": 'a:bar[baz]', 334 | \ "\(textobj-functioncall-a)": 'a:bar[baz]', 335 | \ "\(textobj-functioncall-innerparen-i)": 'baz', 336 | \ "\(textobj-functioncall-innerparen-a)": 'baz', 337 | \ }, 338 | \ }, 339 | \ ] 340 | runtime ftplugin/vim/textobj_functioncall.vim 341 | call s:trytestset(testset) 342 | endfunction 343 | "}}} 344 | function! s:suite.multilines() dict abort "{{{ 345 | let testset = [ 346 | \ { 347 | \ 'buffer': ['call foo(', 'bar', ')'], 348 | \ 'input': '5ly%s', 349 | \ 'expect': { 350 | \ "\(textobj-functioncall-i)": "foo(\nbar\n)", 351 | \ "\(textobj-functioncall-a)": "foo(\nbar\n)", 352 | \ "\(textobj-functioncall-innerparen-i)": "\nbar\n", 353 | \ "\(textobj-functioncall-innerparen-a)": "\nbar\n", 354 | \ }, 355 | \ }, 356 | \ 357 | \ { 358 | \ 'buffer': ['call foo(', 'bar', ')'], 359 | \ 'input': 'jly%s', 360 | \ 'expect': { 361 | \ "\(textobj-functioncall-i)": "foo(\nbar\n)", 362 | \ "\(textobj-functioncall-a)": "foo(\nbar\n)", 363 | \ "\(textobj-functioncall-innerparen-i)": "\nbar\n", 364 | \ "\(textobj-functioncall-innerparen-a)": "\nbar\n", 365 | \ }, 366 | \ }, 367 | \ 368 | \ { 369 | \ 'buffer': ['call foo(', 'bar', ')'], 370 | \ 'input': '2jy%s', 371 | \ 'expect': { 372 | \ "\(textobj-functioncall-i)": "foo(\nbar\n)", 373 | \ "\(textobj-functioncall-a)": "foo(\nbar\n)", 374 | \ "\(textobj-functioncall-innerparen-i)": "\nbar\n", 375 | \ "\(textobj-functioncall-innerparen-a)": "\nbar\n", 376 | \ }, 377 | \ }, 378 | \ 379 | \ { 380 | \ 'buffer': ['call foo(', ' bar', ' )'], 381 | \ 'input': '5ly%s', 382 | \ 'expect': { 383 | \ "\(textobj-functioncall-i)": "foo(\n bar\n )", 384 | \ "\(textobj-functioncall-a)": "foo(\n bar\n )", 385 | \ "\(textobj-functioncall-innerparen-i)": "\n bar\n ", 386 | \ "\(textobj-functioncall-innerparen-a)": "\n bar\n ", 387 | \ }, 388 | \ }, 389 | \ 390 | \ { 391 | \ 'buffer': ['call foo(', ' bar', ' )'], 392 | \ 'input': 'j2ly%s', 393 | \ 'expect': { 394 | \ "\(textobj-functioncall-i)": "foo(\n bar\n )", 395 | \ "\(textobj-functioncall-a)": "foo(\n bar\n )", 396 | \ "\(textobj-functioncall-innerparen-i)": "\n bar\n ", 397 | \ "\(textobj-functioncall-innerparen-a)": "\n bar\n ", 398 | \ }, 399 | \ }, 400 | \ 401 | \ { 402 | \ 'buffer': ['call foo(', ' bar', ' )'], 403 | \ 'input': '2j2ly%s', 404 | \ 'expect': { 405 | \ "\(textobj-functioncall-i)": "foo(\n bar\n )", 406 | \ "\(textobj-functioncall-a)": "foo(\n bar\n )", 407 | \ "\(textobj-functioncall-innerparen-i)": "\n bar\n ", 408 | \ "\(textobj-functioncall-innerparen-a)": "\n bar\n ", 409 | \ }, 410 | \ }, 411 | \ ] 412 | call s:trytestset(testset) 413 | endfunction 414 | "}}} 415 | function! s:suite.nastycases() dict abort "{{{ 416 | let testset = [ 417 | \ { 418 | \ 'buffer': ["call foo(foo, ')')"], 419 | \ 'input': '11ly%s', 420 | \ 'expect': { 421 | \ "\(textobj-functioncall-i)": "foo(foo, ')')", 422 | \ "\(textobj-functioncall-a)": "foo(foo, ')')", 423 | \ "\(textobj-functioncall-innerparen-i)": "foo, ')'", 424 | \ "\(textobj-functioncall-innerparen-a)": "foo, ')'", 425 | \ }, 426 | \ }, 427 | \ 428 | \ { 429 | \ 'buffer': ["call foo(foo, ')')"], 430 | \ 'input': '15ly%s', 431 | \ 'expect': { 432 | \ "\(textobj-functioncall-i)": "foo(foo, ')')", 433 | \ "\(textobj-functioncall-a)": "foo(foo, ')')", 434 | \ "\(textobj-functioncall-innerparen-i)": "foo, ')'", 435 | \ "\(textobj-functioncall-innerparen-a)": "foo, ')'", 436 | \ }, 437 | \ }, 438 | \ 439 | \ { 440 | \ 'buffer': ["call foo('(', foo)"], 441 | \ 'input': '14ly%s', 442 | \ 'expect': { 443 | \ "\(textobj-functioncall-i)": "foo('(', foo)", 444 | \ "\(textobj-functioncall-a)": "foo('(', foo)", 445 | \ "\(textobj-functioncall-innerparen-i)": "'(', foo", 446 | \ "\(textobj-functioncall-innerparen-a)": "'(', foo", 447 | \ }, 448 | \ }, 449 | \ 450 | \ { 451 | \ 'buffer': ["call foo('(', foo)"], 452 | \ 'input': '17ly%s', 453 | \ 'expect': { 454 | \ "\(textobj-functioncall-i)": "foo('(', foo)", 455 | \ "\(textobj-functioncall-a)": "foo('(', foo)", 456 | \ "\(textobj-functioncall-innerparen-i)": "'(', foo", 457 | \ "\(textobj-functioncall-innerparen-a)": "'(', foo", 458 | \ }, 459 | \ }, 460 | \ ] 461 | runtime syntax/vim.vim 462 | call s:trytestset(testset) 463 | endfunction 464 | "}}} 465 | function! s:suite.longbraket() dict abort "{{{ 466 | let g:textobj_functioncall_patterns = [{'header': 'foo', 'bra': '{(', 'ket': ')}', 'footer': ''}] 467 | let testset = [ 468 | \ { 469 | \ 'buffer': ['call foo{(bar)}'], 470 | \ 'input': '5ly%s', 471 | \ 'expect': { 472 | \ "\(textobj-functioncall-i)": "foo{(bar)}", 473 | \ "\(textobj-functioncall-a)": "foo{(bar)}", 474 | \ "\(textobj-functioncall-innerparen-i)": "bar", 475 | \ "\(textobj-functioncall-innerparen-a)": "bar", 476 | \ }, 477 | \ }, 478 | \ 479 | \ { 480 | \ 'buffer': ['call foo{(bar)}'], 481 | \ 'input': '8ly%s', 482 | \ 'expect': { 483 | \ "\(textobj-functioncall-i)": "foo{(bar)}", 484 | \ "\(textobj-functioncall-a)": "foo{(bar)}", 485 | \ "\(textobj-functioncall-innerparen-i)": "bar", 486 | \ "\(textobj-functioncall-innerparen-a)": "bar", 487 | \ }, 488 | \ }, 489 | \ 490 | \ { 491 | \ 'buffer': ['call foo{(bar)}'], 492 | \ 'input': '11ly%s', 493 | \ 'expect': { 494 | \ "\(textobj-functioncall-i)": "foo{(bar)}", 495 | \ "\(textobj-functioncall-a)": "foo{(bar)}", 496 | \ "\(textobj-functioncall-innerparen-i)": "bar", 497 | \ "\(textobj-functioncall-innerparen-a)": "bar", 498 | \ }, 499 | \ }, 500 | \ 501 | \ { 502 | \ 'buffer': ['call foo{(bar)}'], 503 | \ 'input': '14ly%s', 504 | \ 'expect': { 505 | \ "\(textobj-functioncall-i)": "foo{(bar)}", 506 | \ "\(textobj-functioncall-a)": "foo{(bar)}", 507 | \ "\(textobj-functioncall-innerparen-i)": "bar", 508 | \ "\(textobj-functioncall-innerparen-a)": "bar", 509 | \ }, 510 | \ }, 511 | \ ] 512 | call s:trytestset(testset) 513 | 514 | 515 | 516 | let g:textobj_functioncall_patterns = [{'header': '', 'bra': '{(', 'ket': ')}', 'footer': 'foo'}] 517 | let testset = [ 518 | \ { 519 | \ 'buffer': ['call {(bar)}foo'], 520 | \ 'input': '5ly%s', 521 | \ 'expect': { 522 | \ "\(textobj-functioncall-i)": "{(bar)}foo", 523 | \ "\(textobj-functioncall-a)": "{(bar)}foo", 524 | \ "\(textobj-functioncall-innerparen-i)": "bar", 525 | \ "\(textobj-functioncall-innerparen-a)": "bar", 526 | \ }, 527 | \ }, 528 | \ 529 | \ { 530 | \ 'buffer': ['call {(bar)}foo'], 531 | \ 'input': '8ly%s', 532 | \ 'expect': { 533 | \ "\(textobj-functioncall-i)": "{(bar)}foo", 534 | \ "\(textobj-functioncall-a)": "{(bar)}foo", 535 | \ "\(textobj-functioncall-innerparen-i)": "bar", 536 | \ "\(textobj-functioncall-innerparen-a)": "bar", 537 | \ }, 538 | \ }, 539 | \ 540 | \ { 541 | \ 'buffer': ['call {(bar)}foo'], 542 | \ 'input': '11ly%s', 543 | \ 'expect': { 544 | \ "\(textobj-functioncall-i)": "{(bar)}foo", 545 | \ "\(textobj-functioncall-a)": "{(bar)}foo", 546 | \ "\(textobj-functioncall-innerparen-i)": "bar", 547 | \ "\(textobj-functioncall-innerparen-a)": "bar", 548 | \ }, 549 | \ }, 550 | \ 551 | \ { 552 | \ 'buffer': ['call {(bar)}foo'], 553 | \ 'input': '14ly%s', 554 | \ 'expect': { 555 | \ "\(textobj-functioncall-i)": "{(bar)}foo", 556 | \ "\(textobj-functioncall-a)": "{(bar)}foo", 557 | \ "\(textobj-functioncall-innerparen-i)": "bar", 558 | \ "\(textobj-functioncall-innerparen-a)": "bar", 559 | \ }, 560 | \ }, 561 | \ ] 562 | call s:trytestset(testset) 563 | endfunction 564 | "}}} 565 | function! s:suite.independentkeymapping() dict abort "{{{ 566 | onoremap (myfunctioncall-i) :call textobj#functioncall#i('o', [{'header': '\$', 'bra': '{', 'ket': '}', 'footer': ''}]) 567 | xnoremap (myfunctioncall-i) :call textobj#functioncall#i('x', [{'header': '\$', 'bra': '{', 'ket': '}', 'footer': ''}]) 568 | onoremap (myfunctioncall-a) :call textobj#functioncall#a('o', [{'header': '\$', 'bra': '{', 'ket': '}', 'footer': ''}]) 569 | xnoremap (myfunctioncall-a) :call textobj#functioncall#a('x', [{'header': '\$', 'bra': '{', 'ket': '}', 'footer': ''}]) 570 | 571 | let testset = [ 572 | \ { 573 | \ 'buffer': ['call ${foo}'], 574 | \ 'input': '5ly%s', 575 | \ 'expect': { 576 | \ "\(textobj-functioncall-i)": "", 577 | \ "\(textobj-functioncall-a)": "", 578 | \ "\(myfunctioncall-i)": "${foo}", 579 | \ "\(myfunctioncall-a)": "${foo}", 580 | \ }, 581 | \ }, 582 | \ ] 583 | call s:trytestset(testset) 584 | 585 | ounmap (myfunctioncall-i) 586 | xunmap (myfunctioncall-i) 587 | ounmap (myfunctioncall-a) 588 | xunmap (myfunctioncall-a) 589 | endfunction "}}} 590 | 591 | function! s:trytestset(testset) abort "{{{ 592 | for test in a:testset 593 | call s:try(test) 594 | endfor 595 | endfunction 596 | "}}} 597 | function! s:try(test) abort "{{{ 598 | %delete 599 | let @@ = 'nul' 600 | call append(0, a:test.buffer) 601 | for [key, expect] in items(a:test.expect) 602 | let keyseq = printf(a:test.input, key) 603 | normal! gg 604 | execute 'normal ' . keyseq 605 | call g:assert.equals(@@, expect, s:message(keyseq)) 606 | endfor 607 | endfunction 608 | "}}} 609 | function! s:message(keyseq) abort "{{{ 610 | let message = [] 611 | let message += ['input: ' . substitute(a:keyseq, "\", '', 'g')] 612 | let message += ['buffer: '] 613 | let message += getline(1, '$') 614 | return join(message, "\n") . "\n" 615 | endfunction 616 | "}}} 617 | 618 | 619 | 620 | " vim:set foldmethod=marker: 621 | " vim:set commentstring="%s: 622 | --------------------------------------------------------------------------------