こんにちわちわ、10月に新しく入りましたemonkakです。
今回は全国1000万人のVimmerのみなさんに、便利なVimのoperatorについて紹介したいと思います。
opepatorとはお馴染みy(yank)d(delete)などの、範囲を取るコマンドのことです。
operatorはカーソルを移動するwやeなどのmotionに対して使うか、 Visual mode上で使うことができます。
例えば、行の終わりまでyankする場合はy$かv$yと打鍵します。
また、operatorはtext-objectに対して使うことができます。
例えば、yiwと打鍵すると現在のカーソルの下の単語をyankするといった動作になります。
この場合yがoperatorで、iwが範囲を示すtext-objectです。
yyのようにoperatorのマッピングを二回打鍵した場合は、現在の行を表わすtext-objectを渡したことになります。
operatorはtext-objectと組み合わせると非常に便利です。
Visual modeで選択してからopepatorを使うより、text-objectに対して使う方がタイプ数が少なくて済むからです。
これでもうVisual modeは不要ですね。
ここで一度、組込みのoperatorとtext-objectをおさらいしておきましょう。
operator一覧
以下がデフォルトで定義されているoperatorです。
c d yとインデント関連以外はあまり使うことはないかもしれません。
ROT13のoperatorがあるのは面白いです。
| マッピング | 動作 |
|---|---|
c |
変更する |
d |
削除する |
y |
yankする |
~ |
大文字小文字を入れ替える (‘tildeop’がセットされていれば) |
g~ |
大文字小文字を入れ替える |
gu |
小文字にする |
gU |
大文字にする |
! |
外部プログラムでフィルターする |
= |
インデントを整形する |
gq |
テキストを整形する(幅を80に収めたり) |
g? |
ROT13で暗号化する |
> |
右にインデントする |
< |
左にインデントする |
zf |
foldを定義する |
g@ |
‘operatorfunc’に設定された関数を呼び出す |
text-object一覧
text-objectは通常a?、i?というルールで定義されています。
aは英語の冠詞aを表わし、範囲をそのまま示します。
iはinnerの略でその範囲の内部を示します。
例えば、ibは括弧の内部を示し、abは括弧自体を含めた内容を示します。
| マッピング | 範囲 |
|---|---|
aw iw |
単語(iskeywordに依存) |
aW iW |
単語(空白区切り) |
as is |
文 |
ap ip |
段落 |
a] a[ i] i[ |
[] 括弧 |
a) a( ab i) i( ib |
() 括弧 |
a> a< i> i< |
<> 括弧 |
at it |
htmlタグ |
a} a{ aB i{ i} iB |
{} 括弧 |
a" i" |
ダブルクオート |
a' i' |
シングルクオート |
a` i` |
バッククオート |
また、textobj-userを導入すると新しくtext-objectを追加できます。
これでより便利にoperatorを使うことができます。
以下に公開されている有用なtext-objectを列挙しておきます。
使用にはtextobj-userが必要です。
| text-object | 範囲 |
|---|---|
| textobj-datetime | 日付と時刻 |
| textobj-diff | Diff hank |
| textobj-entire | バッファ全体 |
| textobj-fold | Folding |
| textobj-function | 関数 |
| textobj-indent | インデントブロック |
| textobj-jabraces | 日本語の鉤括弧など |
| textobj-lastpat | 最後に検索されたパターン |
| textobj-syntax | Syntax highlightの塊 |
| textobj-comment | コメント |
| textobj-between | 任意の文字に囲まれた範囲 |
| textobj-paramater | 関数のパラメーター |
operator-userで新しくoperatorを追加する
組込みのoperatorは少ないですが、自分で追加することもできます。
operatorを新しく追加するには、operator-userを使います。
こちらも有用なものを列挙しておきます。
使用にはoperator-userが必要です。
| text-object | 動作 |
|---|---|
| operator-replace | レジスタの内容でテキストを置き換える |
| operator-camelize | snake_caseとCamelCaseを相互変換する |
| operator-sequence | 複数のoperatorを連続して使用する |
| operator-html-escape | HTML要素をエスケープする |
| operator-comment | ソースコードをコメント・コメントアウトする |
| operator-sort | テキストをソートする |
operator-commentとoperator-sortは私が作ったプラグインです。
後に動作を詳しく説明します。
私が定義しているoperator
operator-comment
これは、ソースコードをコメント・コメントアウトするoperatorです。
.vimrcに定義していましたが、長くなったのでプラグイン化しました。
コメント文字列は'commentstring'を見ているのでカスタマイズしたい場合は適宜設定して下さい。
マッピングはarpeggioを使って、oと任意のキーの同時押しにマッピングしています。
operatorのマッピングは2回繰り返して押すと行に対する動作になるので、シングルストロークのマッピングの方が便利です。
しかし、キーはなかなか空いてないので、同時押しにマッピングできるarpeggioを使っています。
Arpeggio map oc <Plug>(operator-comment) Arpeggio map od <Plug>(operator-uncomment)
textobj-indentがインストールされていると仮定して、動作は以下のようになります。
Before

この状態で、oとcを同時押ししてtextobj-indentのマッピングiiを打鍵すると、
After

このようにインデントに沿ってコメント化されます。
複数行コメントの構文にも対応していますが、'commentstring'は1つしか設定できないので使い分けることはできません。
また、コメントアウトされた状態で、<operator-uncomment>を実行すると元のコメントアウトされていない状態に戻ります。
operator-sort
これはソートを実行するoperatorです。
こちらもプラグインとして公開しています。
operator-userはコマンドからoperatorを作ることができるので:sortを使って以下のように定義することもできます。
call operator#user#define_ex_command('sort', 'sort')
しかし、行に対してしか動作しないので、文字列に対しても動作するようにプラグインを作りました。
行に対するソートは内部で:sortを呼び出しているので、動作は:sortと同じてす。
マッピングは以下のようにしています。
nmap [Space]S <Plug>(operator-sort)$ nmap [Space]s <Plug>(operator-sort) vmap [Space]s <Plug>(operator-sort)
文字列に対する動作は以下のようになります。

ウィンドウの下の部分がoperatorを実行した結果です。
ここではカーソル位置で[Space]sir,と打鍵しています。
irは[]括弧の内部を示すtext-objectです。
,は区切り文字で、文字列に対して実行する場合は区切りが自明ではないので入力する必要があります。
operator-yank-clipboard
これは単にクリップボードにyankするだけのoperatorです。
noremap "+yで十分だと思われるかもしれませんが、2回繰り返して押した時に正しく動かないので定義しています。
function! OperatorYankClipboard(motion_wiseness)
let visual_commnad =
\ operator#user#visual_command_from_wise_name(a:motion_wiseness)
execute 'normal!' '`['.visual_commnad.'`]"+y'
endfunction
call operator#user#define('yank-clipboard', 'OperatorYankClipboard')
Arpeggio map oy <Plug>(operator-yank-clipboard)
Arpeggio map oy <Plug>(operator-yank-clipboard)
operator-translate
これはGoogle翻訳を使って英日翻訳をするoperatorです。
動作にはwebapi-vimが必要になります。
単にechoするだけのシンプルなものですが、ちょっとした英文を訳したい時に便利です。
function! OperatorTranslate(motion_wiseness)
let visual_commnad =
\ operator#user#visual_command_from_wise_name(a:motion_wiseness)
let query = join(s:get_region("'[", "']", visual_commnad), "\n")
let api = 'http://translate.google.com/translate_a/t'
let response = http#get(api, {
\ 'client': 'o',
\ 'hl': 'en',
\ 'sl': 'en',
\ 'tl': 'ja',
\ 'text': query
\ }, {'User-Agent': 'Mozilla/5.0'})
if response.header[0] ==# 'HTTP/1.1 200 OK'
let result = json#decode(response.content)
echo join(map(result.sentences, 'v:val.trans'))
else
echoerr response.header[0]
end
endfunction
call operator#user#define('translate', 'OperatorTranslate')
Arpeggio map ot <Plug>(operator-translate)
このoperatorはs:get_region()というヘルパー関数を使っています。
このヘルパー関数はgetpos()で取る引数を2つと、visualmode()で得られる値を取ります。
動作としてはgetpos()を2回、それぞれの引数で呼んで、2つの座標間のテキストをgetline()で取得して整形しています。
Visual modeで選択してyankすることでも取得できますが、このヘルパー関数はregisterを汚染しないのとカーソルの移動がないのが利点です。
以下にs:get_region()の定義も載せます。
function! s:get_region(expr1, expr2, visual_commnad) let [lnum1, col1] = getpos(a:expr1)[1:2] let [lnum2, col2] = getpos(a:expr2)[1:2] let region = getline(lnum1, lnum2) if a:visual_commnad ==# "v" " char if lnum1 == lnum2 " single line let region[0] = s:strpart(region[-1], col1 - 1, col2 - (col1 - 1)) else " multi line let region[0] = s:strpart(region[0], col1 - 1) let region[-1] = s:strpart(region[-1], 0, col2) endif elseif a:visual_commnad ==# "V" " line let region += [''] else " block call map(region, 's:strpart(v:val, col1 - 1, col2 - (col1 - 1))') endif return region endfunction
また、末尾に日本語が来た時のために以下の関数も必要です。
function! s:strpart(src, start, ...) let str = strpart(a:src, a:start) if a:0 > 0 let i = byteidx(strpart(str, a:1 - 1), 1) - 1 return i == -1 ? str : strpart(str, 0, a:1 + i) else return str endif endfunction
operator-search
これは渡された範囲の文字列で検索を実行するoperatorです。
これもs:get_region()を使ってます。
カーソル下の単語で検索する*と#のoperator版と言えます。
私はVisual modeで使えれば十分なので:vmapのみしています。
function! OperatorSearch(motion_wiseness)
let visual_commnad =
\ operator#user#visual_command_from_wise_name(a:motion_wiseness)
let search_command = v:searchforward ? 'n' : 'N'
let region = join(map(s:get_region("'[", "']", visual_commnad),
\ 'escape(v:val, "\\/")'),
\ '\n')
let @/ = '\V' . region
call histadd('/', '\V' . region)
silent execute 'normal!' search_command
endfunction
call operator#user#define('search-forward', 'OperatorSearch',
\ 'let v:searchforward = 1')
call operator#user#define('search-backward', 'OperatorSearch',
\ 'let v:searchforward = 0')
vmap * <Plug>(operator-search-forward)
vmap # <Plug>(operator-search-backward)
まとめ
operatorは使いこなすと非常に便利です。
みなさんもoperatorを使いこなして快適なVim lifeを送りましょう。

