Vimテキストオブジェクトの自作+α 

<!-- vi: se tw=1000 noet sw=2 ts=4 sts=2  wrap nolbr et : -->

# 概要

前回の記事「[Vimテキストオブジェクトの自作（手習い）](https://qiita.com/azuwai2/items/5ff49adde4aaddfabc78)」の続きです。

テキストオブジェクトをプラグインとして、一から自作してみようという試みです。

目的は以下の2点でした。

* テキストオブジェクトの1文字目としての s を実現する。（surround.vim 的な）
* テキストオブジェクトの2文字目としての % を実現する。（:h matchpairs参照）

大体目的は達成したので、記事としてまとめてみました。
作成初期の紆余曲折については、[前回の記事](https://qiita.com/azuwai2/items/5ff49adde4aaddfabc78)を参照。

今回は完成品のお披露目です。

前回記事終了時から色々と弄りましたが、修正過程まで書き込むと非常に冗長になるという前回記事の反省を踏まえて、今回は機能説明だけにします。

# 環境

* Microsoft Windows [Version 10.0.19042.685]
* GVIM 8.2.1287 (kaoriya 2020/07/24)

# 最初に

この記事の作成に至った経緯は…

 * surround.vim ( + repeat.vim )
 * vim-surround
 * vim-operator-user + vim-operator-surround
 * vim-sandwich

これらの素晴らしいものを、使ってみたい！
でもプラグインマネージャ入れるのはいやだ。（ネットに繋がってない場所で使うことの方が多い）
そうだ自分で作ってみよう！

・・・というわけではありませんでした。

実のところ、機能としては surround.vim の記事を読むことがあったのですが、便利なのかなー、という程度の認識でした。今回、「テキストオブジェクトを作る」という手段のため、目的として選んでみました。

どれも実際には使ったことはありませんが、自分で作ってみると、これは便利な物なのかもなー、という感じです。ということで、使い勝手については実感を伴わない伝聞でしか知らないので、先達の方々と同じ振る舞いをするとは限りません。

むしろ自分でも`so %`としてチョロチョロ動かしている程度しか使っていない（！）ので、ヘンな動きも残っているかもしれません。**そこの辺りは、実験品という扱いで一つよろしくお願いします。**

上記の先達の方々は、それぞれ独自のポリシーにより多少の方言があるようです。もちろん、物によってはカスタマイズにより好みに変えられるようです。

一応、私の脳内は、最初に知った surround.vim の文法の影響を最も強く受けているのですが、私のヤツの場合は、トリガーになるキーストロークを変えられる程度の、カスタマイズ性というのも烏滸がましい程度の機能しか有しておりません。

# 特徴
* 一応、特徴（だと思っている）のは当初の目的でもあり、プラグイン名にも付けた、マッチペアを扱うテキストオブジェクトです。
* 今更特徴ともいえないのかもしれませんが、自前のドットリピートに対応しています。
* repeat.vim、vim-operator-user、vim-textobj-user に依存しません。
* ファイルを一個置くだけで設定完了。vimrcに何かを追記したりする必要もありません。


# 機能説明

以下のセクションで説明するオペレータ、テキストオブジェクト等が追加されます。
最初から言っておきますが、特に目新しいものはありません。

## オペレータ

以下のオペレータがノーマルモードに追加されます。
キー割り当ては、後述する辞書変数による外部定義で変更することが可能です。

|キー                         |機能                                                         |
|:--                          |:--                                                          |
|cs{surround1}{surround2}     |括り文字{surround1}から{surround2}への変更                   |
|ds{surround}                 |括り文字{surround}の削除                                     |
|ys{textobject}{surround}     |{textobject}の前後に{surround}を追加                         |
|yss{surround}                |該当行に括り文字{surround}を追加<br>（ysil{surround}と同義） |
|gus{surround}                |括り文字の小文字変換(※1)                                    |
|gUs{surround}                |括り文字の大文字変換(※1)                                    |
|g~s{surround}                |括り文字の大文字小文字トグル変換(※1)                        |
|gz{textobject}               |{textobject}を対象に全角→半角変換(※2)                      |
|gZ{textobject}               |{textobject}を対象に半角→全角変換(※2)                      |
|gzs{surround}                |括り文字{surround}の半角→全角変換(※2)                      |
|gZs{surround}                |括り文字{surround}の全角→半角変換(※2)                      |


* ※1.{surround} の「t」にしか意味がありません。（出来るからやってみただけで、使い道があるとは思っていない。。）
* ※2.`hz_ja.vim`（kaoriya版に標準付属）に依存しています。`hz_ja.vim`が存在しない場合、該当機能は有効になりません。

「gz」や「gZ」はともかく、果たして「gzs」や「gZs」に意味はあるのか？ と思われるかもしれませんが、後述する {surround} の「%」と組み合わせて、各種全角括弧を扱ったりすることができるはずです。


以下のオペレータがビジュアルモードに追加されます。
こちらのキー割り当ても、後述する辞書変数による外部定義で変更することが可能です。

|キー                         |機能                                                         |
|:--                          |:--                                                          |
|{visual}S{surround}          |{visual}の文字列の前後に{surround}を追加                     |
|{linewisevisual}S{surround}  |{linewisevisual}の行の前後の行として{surround}を追加         |

## テキストオブジェクト

### {textobject}
以下の{textobject}が追加されます。
キー割り当てを辞書変数による外部定義で変更することが可能です。（al,ilは除く）

|キー                     |機能                                           |
|:--                      |:--                                            |
|a%                       |'matchpairs'の組と、その組に挟まれた文字列     |
|i%                       |'matchpairs'の組に挟まれた文字列               |
|af{char}                 |{char}とその間に挟まれた文字列                 |
|if{char}                 |{char}に挟まれた文字列                         |
|al                       |カーソル行の最初の文字から改行                 |
|il                       |カーソル行の最初の非空白文字から行末           |


「a%」「i%」・・・マッチペアとして定義されている文字の間にカーソルがあるとき、`i%`でその内側を、`a%`でマッチペアを含んで、…という感じに振舞います。
全角括弧を対象にしたかったので、オペレータの内部で一時的に以下のmpsを勝手に追加して動作しています。
  
```
set mps+=（:）,「:」,｛:｝,［:］,【:】,『:』,＜:＞,≪:≫,《:》
```

vimrcで`set mps+=❝:❞`等としていれば、それも対象になります。
・・・が、もし上記のペアから除外したい場合は、直接スクリプトを弄ってもらうしかないです。（このドキュメントを書いている、今この瞬間までここを外部定義にするという発想がなかった）


「af」「if」・・・たぶん、[vim-textobj-between](https://github.com/thinca/vim-textobj-between)と同じです。任意の1文字に挟まれた範囲を扱います。
実は、{surround}として入力したキーがテキストオブジェクトの2文字目に該当しない文字だった場合、「その文字に挟まれた間」として動作させてしまっていたのですが、被った場合に使えないので明確に「その文字に挟まれた間」としての機能を切り出しました。
キー割り当ては外部定義で変更可能です。


「al」「il」・・・これはテキストオブジェクトという程のものではなく、実は単なるマッピングです。
オペレータのモーション(omap)としてだけではなく、ヴィジュアルモードのモーション(vmap)としても定義してあるので、「val」や「vil」として使用することも可能です。
キー割り当ては固定になります。

### {surround}
「a{x}」と「i{x}」が、対で存在しているテキストオブジェクトに対して、「s{x}」として使用することが出来るようになります。
「i{x}」の範囲から「a{x}」の範囲で広がる部分が「s{x}」の対象になります。
※変更元や削除対象として指定可能です。変更先や追加対象の{surround}としては使えません。

上記の{textobject}の追加により、独自の{surround}として、以下のキーが追加されます。

|キー                     |機能                               |
|:--                      |:--                                |
|%                        |'matchpairs'の組そのもの           |
|f{char}                  |カーソル位置の左右に存在する{char} |

「s%」の使い方は、例えば`cs%"`とすれば、`「あいう」`を、`"あいう"`に変更出来て、そのまま次は`【かきく】`の上にカーソルがあるときにドットリピートすると`"かきく"`に変えられるわけですな。

`gzs%`で全角括弧を半角括弧に変えたりできます。（`hz_ja.vim`が必要）

ちなみに、vmapにa{x}とi{x}を定義するだけでも、s{x}が使えるようになってしまうという仕組み上、「al」と「il」に対応して自動的に「sl」が有効になっていますが、「行頭の空白文字と、行末の改行」が対象になるという、使いどころのないものになってしまっています。

もちろん、標準的なテキストオブジェクト（`:h object-select`参照）で"a"と"i"が対で存在しているものもすべて使えます。

いかにも「ザ・サラウンド！」というものから、イマイチ使い道がなさそうなものまでありますが、標準テキストオブジェクトによって使えるようになるものを列挙しておきます。

|キー                     |機能                                                                       |
|:--                      |:--                                                                        |
|]<br> [                  |'['と']'を対象にする                                                       |
|)<br>(<br>b              |'('と')'を対象にする                                                       |
|><br><                   |'<'と'>'を対象にする                                                       |
|t                        |`'<xxx>'`と`'</xxx>'`を対象にする                                          |
|}<br>{<br>B              |'{'と'}'を対象にする                                                       |
|"<br>'<br>`              |引用符を対象にする                                                         |
|w                        |単語(word:英数とアンダースコアの連続)の前後に続く空白部分のみを対象にする  |
|W                        |単語(WORD:非空白文字の連続)の前後に続く空白部分のみを対象にする            |
|s                        |文の前後を対象にする<br>yssのみ、カーソル行を対象にする                    |
|p                        |~~なぜかへんな動きをするので押さないでください~~<br>段落の前後を対象にする。通常は段落の後ろにある空行部分。（EOFとの間に空行がない段落は、段落の前にある空行部分が対象になる）|

~~特別なことはしていないつもりなのですが、pだけまともに動作しません。興味深いです。暇になったらじっくり調べてみたい。~~
内部的には`va{x}`と`vi{x}`を行って範囲の差分を取得していたのですが、その時に行単位の選択に対処できていませんでした。`vap`と`vip`だけが行単位の選択になっていたため、pだけ動かない、ということになってました。

確かに`:h ap`や`:h ip`を読むと、`ビジュアルモードで使われたときは、行単位になります。`と書かれています。勉強になりますね。

## その他追加されるマッピング

以下はオペレータでもテキストオブジェクトでもなく、単なるマッピングです。

### 検索パターン系マッピング

テキストオブジェクトとして検索パターンをどうにか利用しようと考えたのですが、マッピングでできるじゃん、というレベルの内容に落ち付いてしまいました。

検索パターンで正規表現を使えば、色々なものにマッチする対象をドットリピートで次々に置き換えたり、削除したりできるのは何気に便利ではないかと思うのです。

|キー       |機能                                                                                 |
|:-:        |:--                                                                                  |
|cn         |カーソルの前方に存在する最終検索パターンをレジスタに入れずに削除して、挿入を始める   |
|cN         |カーソルの後方に存在する最終検索パターンをレジスタに入れずに削除して、挿入を始める   |
|dn         |カーソルの前方に存在する最終検索パターンをレジスタに入れずに削除する                 |
|dN         |カーソルの後方に存在する最終検索パターンをレジスタに入れずに削除する                 |
|crr        |カーソルの前方に存在する最終検索パターンをレジスタ内容と置き換える                   |
|cRr<br>cRR |カーソルの後方に存在する最終検索パターンをレジスタ内容と置き換える                   |
|crc        |カーソルの前方に存在する最終検索パターンをクリップボード内容と置き換える             |
|cRc<br>cRC |カーソルの後方に存在する最終検索パターンをクリップボード内容と置き換える             |

※カーソルがマッチの上にある時は、それを対象にします

crr は「change replace register」とでも覚えてください。
crc は「change replace clipboard」とでも覚えてください。

英語としてどうなのかとかは気にしたら負けです。

### クリップボード系マッピング

クリップボードを少し身近にします。レジスタとクリップボードを一緒の扱いにしてしまうのは個人的に嫌なので、こんな風にしてみました。というか元々vimrcに書いていたのを持ってきて、ちょっと手直しました。

|キー       |機能                                                         |
|:-:        |:--                                                          |
|gy{motion} |{motion}をクリップボードへコピーする                         |
|{visual}gy |{visual}をクリップボードへコピーする                         |
|gyy        |非空白文字～行末までをコピーをクリップボードへコピーする     |
|gY         |行頭～行末までをコピーをクリップボードへコピーする           |

不要なら削除してください。
`:verbose map gy`等とすれば、どこで定義されているのか見ることができるので、サクッとコメントアウトすればＯＫ。

gyの{motion}には、当プラグインにて追加される`al`や`il`も使えるので`gyal`や`gyil`という使い方もできます。
・・・まぁ、~~`gyal`は`gY`と同じですし、~~`gyil`は`gyy`と同じなのですが。

`gyal`と`gY`は、同じではありませんでした。
`gY`は行頭～行末までをクリップボードにコピーしますが、`gyal`は行頭～改行までをクリップボードにコピーします。

個人的には`gyip`が高評価です。
`vip"*y`だとか、`[jV]"*y`（改行コードが一つ余分に入る）だとかよりも、ずっと押しやすいです。

## 外部定義
### g:matchpairsand_beyondLine

辞書変数で、デフォルトのキーストロークを変更することができます。

|マップ名                               |デフォルトキー |機能               |
|:-:                                    |:--            |:--                |
|<Plug>(MatchpairsandChangeSand)        |cs             |囲み置換           |
|<Plug>(MatchpairsandDeleteSand)        |ds             |囲み削除           |
|<Plug>(MatchpairsandAddSand)           |ys             |囲み追加           |
|<Plug>(MatchpairsandAddSandVisual)     |S              |囲み追加(visual)   |
|<Plug>(MatchpairsandLowerCaseSand)     |gus            |小文字化           |
|<Plug>(MatchpairsandUpperCaseSand)     |gUs            |大文字化           |
|<Plug>(MatchpairsandToggleCaseSand)    |g~s            |大文字小文字トグル |
|<Plug>(MatchpairsandHannkaku)          |gz             |半角化             |
|<Plug>(MatchpairsandZenkaku)           |gZ             |全角化             |
|<Plug>(MatchpairsandInnerMps)          |i%             |マッチペア         |
|<Plug>(MatchpairsandAroundMps)         |a%             |マッチペア         |
|<Plug>(MatchpairsandInnerMpsVisual)    |i%             |マッチペア(visual) |
|<Plug>(MatchpairsandAroundMpsVisual)   |a%             |マッチペア(visual) |
|<Plug>(MatchpairsandInnerFind)         |if             |任意1文字          |
|<Plug>(MatchpairsandAroundFind)        |af             |任意1文字          |
|<Plug>(MatchpairsandInnerFindVisual)   |if             |任意1文字(visual)  |
|<Plug>(MatchpairsandAroundFindVisual)  |af             |任意1文字(visual)  |


例えば、以下のようにすることで、デフォルトでは`a%`,`i%`,`s%`だったものが、それぞれ、`am`,`im`,`sm`に置き換わります。

```.vim
        let g:matchpairsand_keys = {
                    \ '<Plug>(MatchpairsandInnerMps)'        : 'im'
                    \,'<Plug>(MatchpairsandAroundMps)'       : 'am'
                    \,'<Plug>(MatchpairsandInnerMpsVisual)'  : 'im'
                    \,'<Plug>(MatchpairsandAroundMpsVisual)' : 'am'
                    \}
```

ちなみに、デフォルトは内部的にこうなっています。(s:matchpairsand_keys)
  
```.vim
    let s:matchpairsand_keys = {
                \ '<Plug>(MatchpairsandChangeSand)'         : 'cs'
                \,'<Plug>(MatchpairsandDeleteSand)'         : 'ds'
                \,'<Plug>(MatchpairsandAddSand)'            : 'ys'
                \,'<Plug>(MatchpairsandAddSandVisual)'      : 'S'
                \,'<Plug>(MatchpairsandLowerCaseSand)'      : 'gus'
                \,'<Plug>(MatchpairsandUpperCaseSand)'      : 'gUs'
                \,'<Plug>(MatchpairsandToggleCaseSand)'     : 'g~s'
                \,'<Plug>(MatchpairsandHannkaku)'           : 'gz'
                \,'<Plug>(MatchpairsandZenkaku)'            : 'gZ'
                \,'<Plug>(MatchpairsandInnerMps)'           : 'i%'
                \,'<Plug>(MatchpairsandAroundMps)'          : 'a%'
                \,'<Plug>(MatchpairsandInnerMpsVisual)'     : 'i%'
                \,'<Plug>(MatchpairsandAroundMpsVisual)'    : 'a%'
                \,'<Plug>(MatchpairsandInnerFind)'          : 'if'
                \,'<Plug>(MatchpairsandAroundFind)'         : 'af'
                \,'<Plug>(MatchpairsandInnerFindVisual)'    : 'if'
                \,'<Plug>(MatchpairsandAroundFindVisual)'   : 'af'
                \} "}}}
```


### g:matchpairsand_keys
この値が 0 の場合、{surround}はカーソル行内のみが対象になります。
1 を設定した場合は、カーソル行の外側にある囲み文字も対象になります。
デフォルトは 1 です。

カーソル行以外が編集対象になる場合は、囲みの開始位置と終了位置を「行→編集箇所」の順で、一瞬ヴィジュアル選択させることで注意喚起するようにしてあります。

### g:matchpairsand_wait
編集対象になる{surround}に一瞬カーソルを合わせる時に点滅する時間をミリ秒単位で指定します。
デフォルトは50です。
{surround}が複数行にまたがる場合は、指定した時間で2度点滅します。

0を指定すると、カーソル移動を行いません。

# 設定方法

`MatchPairSand.vim`を、'runtimepath'配下のpluginディレクトリに置いてください。

`:h vimfiles`参照の事。

以上。

# 本体

```MatchPairSand.vim
" vi: set et ts=4 sts=4 sw=4 fdm=marker tw=0 :

"Vim global plugin for matchpair text object.
"Last Change:2021/01/15 10:57:50.
"Maintainer:azuwai
"License:This file is placed in the public domain.


scriptencoding utf-8

"二重ロード避け
if exists("g:loaded_matchpairsand")
  finish
endif
let g:loaded_matchpairsand = 1

"互換性オプションを退避して規定値に設定
let s:save_cpo = &cpo
set cpo&vim

"マッピングがタイムアウトしにくくなるように（i%やa%はUMPCを片手持ちしていると1秒以内に打つのが難しい）
"・・・プラグイン内でグローバルな設定を変えるのは、あまりお行儀がよろしくないのでしょうか？
set timeout timeoutlen=3000 ttimeoutlen=100

"設定{{{
    if exists("g:matchpairsand_keys") == 0
        "ユーザ定義のサンプル。
        "こんな感じでデフォルト設定を上書きできます。
        "「i% と a% なんて、Shift が必要だし打ちづらいから、im と am に定義しなおすぜ！」
        "という場合、以下のコメントアウトを外してもいいし、自分の vimrc で定義してもいい。
        "※ただし、[M]atch[P]air だからと言って ip や ap 等は既存のテキストオブジェクトなので避けるのが吉。

        "let g:matchpairsand_keys = {
        "            \ '<Plug>(MatchpairsandInnerMps)'        : 'im'
        "            \,'<Plug>(MatchpairsandAroundMps)'       : 'am'
        "            \,'<Plug>(MatchpairsandInnerMpsVisual)'  : 'im'
        "            \,'<Plug>(MatchpairsandAroundMpsVisual)' : 'am'
        "            \}
    endif

    "行跨ぎするような囲い文字を対象にするかどうか
    if exists("g:matchpairsand_beyondLine") ==0
        let g:matchpairsand_beyondLine = 1
    endif

    if exists("g:matchpairsand_wait") == 0
        let g:matchpairsand_wait = 50
    endif
"}}}

"マッピング{{{

    "単純キーマップ追加{{{
        "i/ a/ をテキストオブジェクトとして定義しようとしたが、やりたいこと的にこれで事足りそうだったので。
        "※ i/ と a/ の違いをどう持たせるのか思いつかなかった、というのもある。
        "正規表現でマッチしたパターンを次々とドットで繰り返せるのが便利そう。
        nnoremap cn "_cgn
        nnoremap cN "_cgN
        nnoremap dn "_dgn
        nnoremap dN "_dgN

        "検索パターンをレジスタ内容で置き換える
        nnoremap crr "_cgn<C-R>"<Esc>
        nnoremap cRr "_cgN<C-R>"<Esc>
        nnoremap cRR "_cgN<C-R>"<Esc>

        "検索パターンをクリップボード内容で置き換える
        nnoremap crc "_cgn<C-R>*<Esc>
        nnoremap cRc "_cgN<C-R>*<Esc>
        nnoremap cRC "_cgN<C-R>*<Esc>

        "クリップボードを少し身近に（でも一緒にするのは嫌）
        "gyip 等だけではなく、当プラグインのテキストオブジェクトとの合わせ技で、 gyil や gyal も発動する。
        "※ gc や gd はバッティングリスクの割に利便性もイマイチ感じられないので無し。
        nnoremap gy "*y
        vnoremap gy "*y
        nnoremap gyy ^"*y$
        nnoremap gY 0"*y$

    "}}}

    "テキストオブジェクトのマッピング(ax,ix) "{{{
        "1行
        onoremap <silent> al :<C-U>normal!0v$<CR>
        vnoremap al 0o$

        "インデント除いた1行
        onoremap <silent> il :<C-U>normal! ^vg_<CR>
        vnoremap il ^og_

    "}}}

    "デフォルトのマッピング定義（「g:matchpairsand_keys」を定義することで上書き可能）{{{
    "要素「マッピングインターフェイス名、キーストローク」
    let s:matchpairsand_keys = {
                \ '<Plug>(MatchpairsandChangeSand)'         : 'cs'
                \,'<Plug>(MatchpairsandDeleteSand)'         : 'ds'
                \,'<Plug>(MatchpairsandAddSand)'            : 'ys'
                \,'<Plug>(MatchpairsandAddSandVisual)'      : 'S'
                \,'<Plug>(MatchpairsandLowerCaseSand)'      : 'gus'
                \,'<Plug>(MatchpairsandUpperCaseSand)'      : 'gUs'
                \,'<Plug>(MatchpairsandToggleCaseSand)'     : 'g~s'
                \,'<Plug>(MatchpairsandHannkaku)'           : 'gz'
                \,'<Plug>(MatchpairsandZenkaku)'            : 'gZ'
                \,'<Plug>(MatchpairsandInnerMps)'           : 'i%'
                \,'<Plug>(MatchpairsandAroundMps)'          : 'a%'
                \,'<Plug>(MatchpairsandInnerMpsVisual)'     : 'i%'
                \,'<Plug>(MatchpairsandAroundMpsVisual)'    : 'a%'
                \,'<Plug>(MatchpairsandInnerFind)'          : 'if'
                \,'<Plug>(MatchpairsandAroundFind)'         : 'af'
                \,'<Plug>(MatchpairsandInnerFindVisual)'    : 'if'
                \,'<Plug>(MatchpairsandAroundFindVisual)'   : 'af'
                \} "}}}

    "マッピングインターフェイス{{{
    "要素「マッピングインターフェイス名、配列［モード、関数呼び出し+第1引数、第3引数、受け取る文字数］」
    "      ※第2引数は自動的にキーストロークを設定する
    let s:matchpairsand_futures = {
                \ '<Plug>(MatchpairsandChangeSand)'         :  [ 'n' , ':<C-U>call <SID>cSurround(v:false'      , ''    , 0 ]
                \,'<Plug>(MatchpairsandDeleteSand)'         :  [ 'n' , ':<C-U>call <SID>dSurround(v:false'      , ''    , 0 ]
                \,'<Plug>(MatchpairsandAddSand)'            :  [ 'n' , ':<C-U>call <SID>ySurround(v:false'      , ''    , 0 ]
                \,'<Plug>(MatchpairsandAddSandVisual)'      :  [ 'v' , ':<C-U>call <SID>sSurround(v:false'      , ''    , 0 ]
                \,'<Plug>(MatchpairsandLowerCaseSand)'      :  [ 'n' , ':<C-U>call <SID>guSurround(v:false'     , 'u'   , 0 ]
                \,'<Plug>(MatchpairsandUpperCaseSand)'      :  [ 'n' , ':<C-U>call <SID>guSurround(v:false'     , 'U'   , 0 ]
                \,'<Plug>(MatchpairsandToggleCaseSand)'     :  [ 'n' , ':<C-U>call <SID>guSurround(v:false'     , '~'   , 0 ]
                \,'<Plug>(MatchpairsandHannkaku)'           :  [ 'n' , ':<C-U>call <SID>gzOperator(v:false'     , 'gHL' , 0 ]
                \,'<Plug>(MatchpairsandZenkaku)'            :  [ 'n' , ':<C-U>call <SID>gzOperator(v:false'     , 'gZL' , 0 ]
                \,'<Plug>(MatchpairsandInnerMps)'           :  [ 'o' , ':<C-U>call <SID>mpsObject(v:false'      , 'i'   , 0 ]
                \,'<Plug>(MatchpairsandAroundMps)'          :  [ 'o' , ':<C-U>call <SID>mpsObject(v:false'      , 'a'   , 0 ]
                \,'<Plug>(MatchpairsandInnerMpsVisual)'     :  [ 'v' , ':<C-U>call <SID>mpsObject(v:false'      , 'i'   , 0 ]
                \,'<Plug>(MatchpairsandAroundMpsVisual)'    :  [ 'v' , ':<C-U>call <SID>mpsObject(v:false'      , 'a'   , 0 ]
                \,'<Plug>(MatchpairsandInnerFind)'          :  [ 'o' , ':<C-U>call <SID>findObject(v:false'     , 'i'   , 0 ]
                \,'<Plug>(MatchpairsandAroundFind)'         :  [ 'o' , ':<C-U>call <SID>findObject(v:false'     , 'a'   , 0 ]
                \,'<Plug>(MatchpairsandInnerFindVisual)'    :  [ 'v' , ':<C-U>call <SID>findObject(v:false'     , 'i'   , 1 ]
                \,'<Plug>(MatchpairsandAroundFindVisual)'   :  [ 'v' , ':<C-U>call <SID>findObject(v:false'     , 'a'   , 1 ]
                \} "}}}

    "リピート対応{{{
    nnoremap <silent> . :<C-U>call <SID>dotRepeat()<CR>
    nnoremap <silent> u :<C-U>call <SID>undoRepeat("u")<CR>
    nnoremap <silent> U :<C-U>call <SID>undoRepeat("U")<CR>
    "}}}

    "Vim起動時のマッピング追加{{{
    au VimEnter * call <SID>map_init()
    function! s:map_init()
        "ユーザー定義を優先的に設定
        if exists("g:matchpairsand_keys")
            call <SID>map_set(g:matchpairsand_keys)
        endif
        "デフォルト定義を設定
        call <SID>map_set(s:matchpairsand_keys)
    endfunction
    "}}}
"}}}

"イベント{{{
    au TextChanged,TextChangedI,TextChangedP * call <SID>textChanged()
    fu! s:textChanged() "{{{
        if exists("b:dotReady")
            "テキストが変更されたときに、-1する。
            let b:dotReady = ( b:dotReady==0 ? 0 : eval(b:dotReady - 1) )
        endif
    endf "}}}
"}}}

"関数 {{{
    "マッピング設定{{{
    fu! s:map_set(dict)
        for mapifs in keys(a:dict)
            if !exists("*HzjaConvert")
                if stridx(s:matchpairsand_futures[mapifs][1] ,'gzOperator') != -1
                    continue
                endif
            endif
            if !hasmapto(mapifs)
                "未定義の場合のみ設定する。（ユーザー定義を上書きしない）
                "定義されているキーストロークを取得し、マッピングインターフェイスへマップ
                let keystroke = a:dict[mapifs]
                exec s:matchpairsand_futures[mapifs][0].'map <unique> ' . keystroke . ' ' . mapifs
                "マッピングインターフェイスから関数へのマッピングを実施。キーストロークを引数に埋め込む。
                "exec s:matchpairsand_futures[mapifs][0].'noremap <silent> ' . keystroke . ' ' .
                exec s:matchpairsand_futures[mapifs][0].'noremap <silent> ' . mapifs . ' ' .
                            \ s:matchpairsand_futures[mapifs][1] . ',"' . keystroke . '","' .
                            \ s:matchpairsand_futures[mapifs][2] . '")<CR>'
            endif
        endfor
    endf "}}}

    "オペレータ{{{
        fu! s:cSurround(dotRep, keystroke, ...) abort "{{{
            "ドットリピート用に関数名を保持
            let b:dotRepeatFunc=s:funcName(expand("<sfile>"))
            "echom "start!! " . b:dotRepeatFunc
            let s:cmd = a:keystroke
            let b:keystroke = a:keystroke

            try
                set lazyredraw
                let wi_save = winsaveview()

                "囲い部分の範囲を取得
                if s:getSurround(a:dotRep)
                    return
                endif

                "ドットリピート時は再取得しない
                if a:dotRep == v:false
                    "変更後の囲い文字を取得
                    call s:getAfter()
                endif

                try
                    let ai_save = &ai | setl noai
                    let ww_save = &ww | let &ww = "h,l"
                    "囲い終わり箇所の置き換え
                    if s:aEnd != s:iEnd
                        call setpos('.',s:iEnd) | normal! lv
                        call setpos('.',s:aEnd)
                        exec "normal! c".b:afterEnd
                    else
                        call setpos('.',s:iEnd) | exec "normal! a".b:afterEnd
                    endif

                    "囲い始め箇所の置き換え
                    if s:aStart != s:iStart
                        call setpos('.',s:aStart) | normal! v
                        call setpos('.',s:iStart) | normal! h
                        exec "normal! c".b:afterStart
                    else
                        call setpos('.',s:iStart) | exec "normal! i".b:afterStart
                    endif
                finally
                    let &ai = ai_save
                    let &ww = ww_save
                endtry

                "自身の終了時にイベントで-1されるため、2を設定
                let b:dotReady = 2
            catch /InvalidlInput/
                return
            finally
                set nolazyredraw
                call winrestview(wi_save)
                redraw! | echo ""
            endtry
        endf "}}}

        fu! s:dSurround(dotRep, keystroke, ...) abort "{{{
            "ドットリピート用に関数名を保持
            let b:dotRepeatFunc=s:funcName(expand("<sfile>"))
            "echom "start!! " . b:dotRepeatFunc
            let s:cmd = a:keystroke
            let b:keystroke = a:keystroke

            try
                set lazyredraw
                let wi_save = winsaveview()

                "囲い部分の範囲を取得
                if s:getSurround(a:dotRep)
                    return
                endif

                try
                    let ai_save = &ai | setl noai
                    let ww_save = &ww | let &ww = "h,l"
                    "囲い終わり箇所を削除
                    if s:iEnd != s:aEnd
                        call setpos('.',s:iEnd) | normal! lv
                        call setpos('.',s:aEnd) | normal! d
                    endif

                    "囲い始め箇所を削除
                    if s:aStart != s:iStart
                        call setpos('.',s:aStart) | normal! v
                        call setpos('.',s:iStart) | normal! hd
                    endif
                finally
                    let &ai = ai_save
                    let &ww = ww_save
                endtry

                "自身の終了時にイベントで-1されるため、2を設定
                let b:dotReady = 2
            catch /InvalidlInput/
                return
            finally
                set nolazyredraw
                call winrestview(wi_save)
                redraw! | echo ""
            endtry
        endfu "}}}

        fu! s:ySurround(dotRep, keystroke, ...) abort "{{{
            "ドットリピート用に関数名を保持
            let b:dotRepeatFunc=s:funcName(expand("<sfile>"))
            "echom "start!! " . b:dotRepeatFunc
            let s:cmd = a:keystroke
            let b:keystroke = a:keystroke

            try
                set lazyredraw
                let wi_save = winsaveview()

                "囲う部分のテキストオブジェクト部分を取得
                if s:getMotion(a:dotRep)
                    return
                endif

                "ドットリピート時は再取得しない
                if a:dotRep == v:false
                    "追加する囲い文字を取得
                    call s:getAfter()
                endif

                try
                    let ai_save = &ai | setl noai
                    "囲いを追加
                    call setpos('.',s:iEnd)     | exec "normal! a".b:afterEnd
                    call setpos('.',s:iStart)   | exec "normal! i".b:afterStart
                finally
                    let &ai = ai_save
                endtry

                "自身の終了時にイベントで-1されるため、2を設定
                let b:dotReady = 2
            catch /InvalidlInput/
                return
            finally
                set nolazyredraw
                call winrestview(wi_save)
                redraw! | echo ""
            endtry
        endf "}}}

        fu! s:sSurround(dotRep, keystroke, ...) abort "{{{
            "ドットリピート用に関数名を保持
            let b:dotRepeatFunc=s:funcName(expand("<sfile>"))
            "echom "start!! " . b:dotRepeatFunc
            let s:cmd = a:keystroke
            let b:keystroke = a:keystroke

            try
                set lazyredraw
                let wi_save = winsaveview()
                "let &selection = "inclusive"   "&selectionを変えれば、vやVの時のSでも1行単位にできるかも？
                                                "yssを使えば良いので、あまり必要性を感じないが…

                if a:dotRep == v:false
                    "再選択
                    normal! gv
                    let b:sSuMode = mode()
                    "囲い範囲を取得
                    let iEnd = getcurpos()    | normal! o
                    let iStart = getcurpos()  | exec "normal! ". b:sSuMode
                    if iStart[1] == iEnd[1]
                        "開始終了位置が、同一行の場合
                        if iEnd[2] < iStart[2]
                            "開始終了列が逆転している場合
                            let wk = iStart | let iStart = iEnd | let iEnd = wk
                        endif
                    elseif iEnd[1] < iStart[1]
                        "開始終了行が逆転している場合
                        let wk = iStart | let iStart = iEnd | let iEnd = wk
                    endif
                    "相対位置を記憶
                    let b:vRight = iEnd[2] - iStart[2]
                    let b:vDown = iEnd[1] - iStart[1]
                    "開始位置にカーソルを合わせる
                    call setpos('.',iStart)

                    "追加する囲い文字を取得
                    call s:getAfter()
                endif

                try
                    let ai_save = &ai | setl noai
                    let ww_save = &ww | let &ww = "h,l"
                    "setl paste

                    let iStart = getcurpos()
                    "囲い開始
                    if b:sSuMode==# 'v'
                        exec "normal! i".b:afterStart."\<ESC>"
                    elseif b:sSuMode==# 'V'
                        exec "normal! I".b:afterStart."\<CR>"
                    endif

                    let wk = getcurpos()
                    if 0 < b:vDown
                        let wk[1] += b:vDown
                        let wk[2] -= 1
                        call setpos('.',wk)
                        endif
                            let wk[2] += b:vRight+1
                        call setpos('.',wk)

                    "囲い終わり
                    let iEnd = getcurpos()
                    if b:sSuMode==# 'v'
                        exec "normal! a".b:afterEnd
                    elseif b:sSuMode==# 'V'
                        "行単位ヴィジュアルモードだった場合は、成形しておく。
                        exec "normal! A\<CR>".b:afterEnd
                        call setpos('.',iStart) | normal! V
                        call setpos('.',iEnd) | normal! j=
                    endif

                finally
                    let &ai = ai_save
                    let &ww = ww_save
                    "setl nopaste
                endtry

                "自身の終了時にイベントで-1されるため、2を設定
                let b:dotReady = 2
            catch /InvalidlInput/
                return
            finally
                set nolazyredraw
                call winrestview(wi_save)
                redraw! | echo ""
            endtry
        endf "}}}

        fu! s:guSurround(dotRep, keystroke, ...) abort "{{{
            "ドットリピート用に関数名を保持
            let b:dotRepeatFunc=s:funcName(expand("<sfile>"))
            "echom "start!! " . b:dotRepeatFunc
            let s:cmd = a:keystroke
            let b:keystroke = a:keystroke
            let b:case = a:1

            try
                set lazyredraw
                let wi_save = winsaveview()

                "囲い部分の範囲を取得
                if s:getSurround(a:dotRep)
                    return
                endif

                try
                    let ai_save = &ai | setl noai
                    let ww_save = &ww | let &ww = "h,l"

                    "囲い終わり箇所を処理
                    call setpos('.',s:iEnd) | normal! lv
                    exec "call setpos('.',s:aEnd) | normal! g".b:case

                    "囲い始め箇所を処理
                    call setpos('.',s:aStart) | normal! v
                    exec "call setpos('.',s:iStart) | normal! hg".b:case
                finally
                    let &ai = ai_save
                    let &ww = ww_save
                endtry

                "自身の終了時にイベントで-1されるため、2を設定
                let b:dotReady = 2
            catch /InvalidlInput/
                return
            finally
                set nolazyredraw
                call winrestview(wi_save)
                redraw! | echo ""
            endtry
        endfu "}}}

        fu! s:gzOperator(dotRep, keystroke, ...) abort "{{{
            "ドットリピート用に関数名を保持
            let b:dotRepeatFunc=s:funcName(expand("<sfile>"))
            "echom "start!! " . b:dotRepeatFunc
            let s:cmd = a:keystroke
            let b:keystroke = a:keystroke

            try
                set lazyredraw
                let wi_save = winsaveview()

                if a:dotRep == v:false
                    let b:case = a:1
                    let b:gzWhere = s:getchar()
                endif

                if b:gzWhere == 's'
                    "let s:cmd .= b:gzWhere
                    "囲い部分の範囲を取得
                    if s:getSurround(a:dotRep)
                        return
                    endif

                    try
                        let ai_save = &ai | setl noai
                        let ww_save = &ww | let &ww = "h,l"

                        "囲い終わり箇所を処理
                        call setpos('.',s:iEnd) | normal! lv
                        exec "call setpos('.',s:aEnd) | normal ".b:case

                        "囲い始め箇所を処理
                        call setpos('.',s:aStart) | normal! v
                        exec "call setpos('.',s:iStart) | normal h".b:case
                    finally
                        let &ai = ai_save
                        let &ww = ww_save
                    endtry

                else
                    call s:getMotion(a:dotRep,b:gzWhere)
                    try
                        let ai_save = &ai | setl noai
                        call setpos('.',s:iStart) | normal! v
                        exec "call setpos('.',s:iEnd) | normal ".b:case
                    finally
                        let &ai = ai_save
                    endtry
                endif

                "自身の終了時にイベントで-1されるため、2を設定
                let b:dotReady = 2
            catch /InvalidlInput/
                return
            finally
                set nolazyredraw
                call winrestview(wi_save)
                redraw! | echo ""
            endtry
        endfu "}}}

    "}}}

    "テキストオブジェクト{{{
    "※dotRepは必ずfalse。keystrokeはs:getchar()時に使用。

        fu! s:mpsObject(dotRep, keystroke, ...) abort "{{{
            let i_a = a:1

            normal! %
            let wk1 = getcurpos() | normal! %
            let wk2 = getcurpos()

            if wk1 == wk2
                "%でカーソルが移動しない場合は、何も選択しない
                return
            endif

            let flg_reverse = 0
            if wk1[1] > wk2[1]
                "行が逆転
                let flg_reverse = 1
            elseif wk1[1] == wk2[1]
                if wk1[2] > wk2[2]
                    "同一行だが、列が逆転
                    let flg_reverse = 1
                endif
            endif
            if flg_reverse
                "現在カーソルが開始側にいる場合、終了側へ移動させる。
                normal! %
            endif

            if i_a == "a"
                "外側を選択する
                exec "normal! %v%"

            elseif i_a == "i"
                "内側を選択する
                try
                    let ww_save = &ww | let &ww = "h,l"
                    "一つ左に戻って（行跨ぎ）、選択開始して、元の位置に戻る。
                    exec "normal! hvl"
                    "マッチペアへ移動して(%)、一つ右へ移動(行跨ぎ)して、選択範囲決定。
                    exec "normal! %l"
                finally
                    let &ww = ww_save
                endtry
                normal! o
            endif

        endf "}}}

        fu! s:findObject(dotRep, keystroke, ...) abort "{{{
            let i_a = a:1
            "let s:cmd = a:keystroke

            try
                set lazyredraw
                "winsaveview()/winrestview()はしない。
                "（vで選択する場合等、開始位置に戻るのはNG）

                if exists("b:dotExe") == 0  || b:dotExe == 0
                    let b:findObjChar = s:getchar(0)
                endif
                if exists("b:findObjChar") == 0
                    "存在しない場合(前回getChar()でESCし、かつ、ドットリピート)
                    return 1
                endif

                let hitStart = 0
                let hitEnd = 0

                if i_a == 'i'
                    "内側の範囲を取得
                    let cur_save = getcurpos()
                    exec "normal! F". b:findObjChar
                    if getcurpos() != cur_save
                        let hitStart = 1
                    endif
                    normal! lv
                    let cur_save = getcurpos()
                    exec "normal! ,"
                    if getcurpos() != cur_save
                        let hitEnd = 1
                    endif
                    normal! h
                    if hitStart == 0 && hitEnd ==0
                        normal! v
                    endif
                elseif i_a == 'a'
                    "外の範囲を取得
                    let cur_save = getcurpos()
                    exec "normal! F". b:findObjChar
                    if getcurpos() != cur_save
                        let hitStart = 1
                    endif
                    normal! v
                    let cur_save = getcurpos()
                    exec "normal! ,"
                    if getcurpos() != cur_save
                        let hitEnd = 1
                    endif
                    if hitStart == 0 && hitEnd ==0
                        normal! v
                    endif
                endif
            catch /InvalidlInput/
                if exists("b:findObjChar")
                    unlet b:findObjChar
                endif
                "exec "normal! \<Esc>\<Esc>"
                "exec ":stopi"
                "return 1
            finally
                set nolazyredraw
            endtry

        endf "}}}

    "}}}

    "共通処理{{{

        fu! s:getchar(...) "{{{
            if exists("s:cmd")
                redraw | echo s:cmd
            endif
            let wk = nr2char(getchar())

            if wk !~ '[[:graph:]]'
                redraw | echo '' | throw "InvalidlInput"
            endif
            if exists("s:cmd")
                if a:0==0 || a:1==1
                    "可変引数無し、または、1が指定されている場合は、表示文字列として編集
                    "(編集したくないときに0を指定)
                    let s:cmd .= wk
                endif
                redraw | echo s:cmd
            endif
            return wk
        endfu "}}}

        "現在の囲い文字の入力を受け付けて、その範囲を取得する
        fu! s:getSurround(dotRep) abort "{{{
            try
                "囲う対象を取得
                if a:dotRep == v:true
                    "ドットリピート時は前回値を使用
                    let what = b:what
                else
                    let what = s:getchar()
                    let b:what = what
                endif

                "すでにビジュアルモードの場合、モードを終了する。
                if mode() ==? "v"
                    exec "normal! " . mode()
                endif

                "cmd文字列の変遷の確認用
                "let s:dbg_cnt=0 | mes clear
                "let s:dbg_cnt+=1 | redraw! |  echom s:dbg_cnt . s:cmd | sleep 2

                let wh2 = s:getCustomMotion("i",what)
                let what = wh2[1]

                "内側の範囲を取得
                "※nomalに!を付けないことで、他で定義されたtext-objectを有効にする
                exec "normal vi".what
                if mode() ==# "v"
                    let s:iEnd = getcurpos()    | normal! o
                    let s:iStart = getcurpos()  | normal! ov
                elseif mode() ==# "V"
                    normal! $
                    let s:iEnd = getcurpos()    | normal! o
                    let s:iStart = getcurpos()  | normal! oV
                else
                    let s:iStart= getcurpos()
                    let s:iEnd= getcurpos()
                endif

                "外側の範囲を取得
                exec "normal va".what
                if mode() ==# "v"
                    let s:aEnd = getcurpos()    | normal! o
                    let s:aStart = getcurpos()  | normal! v
                elseif mode() ==# "V"
                    normal $
                    let s:aEnd = getcurpos()    | normal! o
                    let s:aStart = getcurpos()  | normal! V
                else
                    let s:aStart= getcurpos()
                    let s:aEnd= getcurpos()
                endif


                if s:iStart[1] != s:iEnd[1]
                    "同一行内ではない場合
                    if g:matchpairsand_beyondLine == 0
                        "行を超える設定ではない場合
                        return 1
                    endif
                endif

                "テキストオブジェクト(ix,ax)として見つからなかった場合
                if s:aStart == s:aEnd
                    return 1
                endif

                "範囲確認用
                "message clear
                "echom "s:"s:aStart[1]. "/" . s:aStart[2]. " - " .s:iStart[1] . "/" . s:iStart[2]
                "echom "e:"s:iEnd[1]  . "/" . s:iEnd[2]  . " - " .s:aEnd[1]   . "/" . s:aEnd[2]

                if g:matchpairsand_wait > 0
                    if s:aStart[1] != s:aEnd[1]
                        for i in range(1,2)
                            call setpos('.',s:aStart) | normal! V
                            redraw | exec "sleep ".g:matchpairsand_wait."m"
                            if s:aStart[1] == s:iStart[1]
                                call setpos('.',s:iStart) | normal! hv
                            endif
                            redraw | exec "sleep ".eval(g:matchpairsand_wait*2)."m"
                            exec "normal! ".mode()
                            redraw | exec "sleep ".g:matchpairsand_wait."m"
                        endfor

                        for i in range(1,2)
                            call setpos('.',s:aEnd)   | normal! V
                            redraw | exec "sleep ".g:matchpairsand_wait."m"
                            if s:aEnd[1] == s:iEnd[1]
                                call setpos('.',s:iEnd)   | normal! lv
                            endif
                            redraw | exec "sleep ".eval(g:matchpairsand_wait*2)."m"
                            exec "normal! ".mode()
                            redraw | exec "sleep ".g:matchpairsand_wait."m"
                        endfor
                    else
                        for i in range(1,2)
                            call setpos('.',s:aStart) | normal! v
                            call setpos('.',s:iStart) | normal! hv
                            redraw | exec "sleep ".g:matchpairsand_wait."m"
                        endfor

                        for i in range(1,2)
                            call setpos('.',s:aEnd)   | normal! v
                            call setpos('.',s:iEnd)   | normal! lv
                            redraw | exec "sleep ".g:matchpairsand_wait."m"
                        endfor
                    endif
                endif

            catch /InvalidlInput/
                return 1
            endtry

            return 0
        endf "}}}

        "変更後/追加する囲い文字の入力を受け付ける
        fu! s:getAfter() abort "{{{
            try
                let wk = s:getchar()

                if wk ==# "b"
                    let wk = "("
                elseif wk ==# "B"
                    let wk = "{"
                endif


                if wk == '<'
                    "タグの場合は、開始タグ・終了タグ
                    let l:tagStart =""
                    while wk != '>'
                        let l:tagStart .= wk
                        let wk = s:getchar()
                    endwhile
                    let l:tagStart .= wk

                    let b:afterStart = l:tagStart
                    let b:afterEnd   = substitute(l:tagStart,'<','</','')

                else
                    "タグ以外の場合は、対になる記号、または、同一記号
                    let pairs = s:getPairs(wk)
                    let b:afterStart =pairs[0]
                    let b:afterEnd   =pairs[1]
                endif
            catch /InvalidlInput/
                throw "InvalidlInput"
            endtry
            return 0
        endf "}}}

        "囲い文字の組み合わせを取得する
        fu! s:getPairs(surround) "{{{
            let mps_save = &mps
            "アングルブラケットをマッチペアに追加※「<」はタグ入力のトリガーなので、「>」用
            set mps+=<:>

            "全角カッコ系を追加
            set mps+=（:）,「:」,｛:｝,［:］,【:】,『:』,＜:＞,≪:≫,《:》

            "マッチペアを検索
            let flgMps = 0
            for mp in split(&mps,',')
                let pairs = split(mp,':')
                if a:surround == pairs[0] || a:surround == pairs[1]
                    let flgMps = 1 | break
                endif
            endfor

            "マッチペアを戻す
            let &mps = mps_save

            if flgMps == 0
                "マッチペアに該当しない場合は、同じ文字を設定する。
                let pairs[0] = a:surround
                let pairs[1] = a:surround
            endif

            return pairs

        endf "}}}

        "変更前のテキストオブジェクトの入力を受け付ける
        fu! s:getMotion(dotRep,...) abort "{{{
            let l:count = ""
            let l:where = ""
            let l:what  = ""

            try
                "モーション文字列を取得
                if a:dotRep == v:true
                    "ドットリピート時は前回値を使用
                    let l:count = b:count
                    let l:where = b:where
                    let l:what  = b:what
                else
                    "リピート時以外は、テキストオブジェクトの入力を受け付ける
                    if a:0
                        "可変引数あり(gzOperator()から呼び出し)時
                        "すでに1文字目が入力されているとみなす
                        let wk = a:1
                        redraw | echo s:cmd
                    else
                        "可変引数なし(ySurround()から呼び出し)
                        let wk = s:getchar()
                    endif

                    while wk =~ "[0-9]"
                        "数字が入力され続ける間、[count]に保持
                        let l:count .= wk
                        let wk = s:getchar()
                    endwhile

                    if wk =~ "^[ia]$"
                        "テキストオブジェクトの場合
                        let l:where = wk
                        "もう1文字受け取ってテキストオブジェクトを完成させる
                        let wk = s:getchar()
                        if wk =~ "[0-9]"
                            "数字が入力された場合
                            if l:count == ""
                                "以前にcountが入力されていない場合、[count]として取得
                                while wk =~ "[0-9]"
                                    let l:count .= wk
                                    let wk = s:getchar()
                                endwhile
                            else
                                "以前にcountが入力されていた場合は、処理中断
                                throw "InvalidlInput"
                            endif
                        endif
                    endif
                    "受け取った文字を連結(カーソル移動の場合も含む)
                    let l:what = wk

                    let wh2 = s:getCustomMotion(l:where,l:what)
                    let l:where = wh2[0]
                    let l:what = wh2[1]

                    let b:count = l:count
                    let b:where = l:where
                    let b:what  = l:what
                endif

                "モーションの範囲を取得
                exec "normal v".l:count.l:where.l:what
                let s:iEnd = getcurpos()    | normal! o
                let s:iStart = getcurpos()  | normal! v
            catch /InvalidlInput/
                throw "InvalidlInput"
            endtry

            return 0
        endf "}}}

        fu! s:getCustomMotion(where,what) "{{{
            if a:where == "" && a:what =="s"
                "yss→ysil的な
                let wh2 = [ "i", "l" ]
                return wh2
            endif

            let wh2 = [ a:where, a:what ]

            "自前のテキストオブジェクトで、2文字で完結しない場合のための処理
            for mapifs in keys(s:matchpairsand_futures)
                "入力を受け取る文字数
                let len = s:matchpairsand_futures[mapifs][3]

                "入力が必要なテキストオブジェクトの場合
                if 0 < len
                    "キーストローク(デフォルト値)を取得
                    let keystroke = s:matchpairsand_keys[mapifs]
                    "キーストローク（ユーザ定義があれば）を取得
                    if exists("g:matchpairsand_keys")
                        let keystroke = get(g:matchpairsand_keys,mapifs,keystroke)
                    endif

                    "現在、入力中のテキストオブジェクトだった場合
                    if wh2[0].wh2[1] == keystroke
                        let wk = ""
                        "必要な文字数分を受け取って
                        while len(wk) < len
                            let wk .= s:getchar()
                        endwhile
                        "whatを置き換えて終了
                        let wh2[1] .= wk
                        break
                    endif
                endif
            endfor

            return wh2

        endf "}}}
    "}}}

    "ドットリピート関連{{{
        "リピート
        fu! s:dotRepeat() "{{{
            let b:dotExe = 1
            if exists("b:dotReady") && b:dotReady != 0
                if v:count!=0
                    let b:count = v:count
                endif
                silent exec 'call <SID>'.b:dotRepeatFunc.'('.v:true.',"'.b:keystroke.'")' | redraw
            else
                silent normal! .
            endif
            let b:dotExe = 0
        endf "}}}

        "アンドゥ後のリピートに備える
        fu! s:undoRepeat(cmdUndo) "{{{
            if exists("b:dotReady") && b:dotReady != 0
                let b:dotReady = 2
            endif
            exec "normal! ".a:cmdUndo
        endf "}}}

        "リピート時の関数名を取得する
        fu! s:funcName(sfile) "{{{
            return substitute(a:sfile,".*_","","")
        endf "}}}
    "}}}
"}}}


"互換性オプションを復元
let &cpo = s:save_cpo
unlet s:save_cpo
```

# 最後に
完成してしまうとあっけないものです。
自分でも本格的に使うかどうかはわかりませんが、とりあえず作っている間は面白かったです。

おそらく、vim-textobj-userを使って、matchpairsにだけ対応するものを公開したほうが、スリムで利便性も高くて、利用してもらい甲斐もあるのでしょうが、今のところやる気はないです。