vim-users.jp

Hack #213: ダブルクリックで単語検索するようにする

Posted at 2011/04/16
このエントリーをはてなブックマークに追加

きっかけ

Vimユーザの方は一日を端末で過ごしている方も多いでしょう。 しかしマウスとはとても便利なものです。 トラックボールを使ったりすると新しい発見があるかもしれません。

キーボードで操作している時はキーボードで操作でき、 マウスで操作している時はマウスで最低限の操作はできるというのが理想です。 最低限の操作とは何でしょうか。 結論から言いますと、あなたがマウスでコードを見ている時必要だと思うことは、大半が単語検索でしょう。

  • 離れた所にある同じ単語に飛ぶ
  • 変数がどこで定義されているか調べる
  • 検索目的ではなくバッファ中にあるテキストをハイライトさせたい

など単語検索でできることは実に様々です。

自分は普通にコードを書いている時でも/コマンドをよく使います。 移動の80%はこれといっていいかもしれません。 言い過ぎだとしても70割かもしれません。 それほど汎用的なコマンドなのです。

(脱線) IDEの場合

IDEだとしたら変数にマウスオーバーした時点で定義場所などの情報がポップアップで表示されるかもしれません。 ただこういう機能は「便利すぎて」ユーザが能動的に情報を探そうとした場合邪魔になることが多いと思うのです。 ちなみにマウスオーバーした時点でポップアップするようなIDEを筆者は知りません。勘で言いました。

自動補完にもこういう側面があると私は思います。 キーをタイプしたらポップアップがめまぐるしく表示され、 頭の中にあったコードがデリートされてしまうなどといったことがありました。

Twitterでチカチカしたアイコンをした人をフォローしてる人には身に覚えがあるかもしれませんが、 気分転換にTwitterを見たらTLにアイコンがチカチカした人がいてとても気分を害されたということがあります。 この対処法としてはtermtterなどの画像が表示されないTwitterクライアントを使うことです。 ちなみにTwitter社はサードパーティ製クライアントを推奨していません

ちなみに筆者は以前自動補完はあまり好きではありませんでしたが慣れました。 自動補完プラグインとしてはautocomplpopneocomplcacheというプラグインが代表的です。 自動補完はとても便利な機能なのでぜひ使いましょう。

閑話休題

無理矢理話を戻すと、マウスでの単語検索を快適にするために必要な設定を本稿では解説したいと思います。

ダブルクリックへのマッピング

ダブルクリックへのマッピングは<2-LeftMouse>を使えばできます。

nnoremap <2-LeftMouse> g*

これで終わりでしょうか?いいえ、まだです。

これだとダブルクリックをしようとした時、 シングルクリック(<LeftMouse>)の時点で<LeftMouse>本来の操作、 つまりクリックした場所にカーソルを移動、が実行されてしまうのです。 カーソルが移動するのは多いに結構なのですが、 問題は'scrolloff'の設定も効いてしまうことです。

'scrolloff'は正の値をセットするとカーソルの上部か下部が 常に'scrolloff'分の行だけ表示されるようになるという非常に便利なオプションですが、 これがマウスのシングルクリックの時に効いてしまうと

  1. シングルクリック (クリック1回目)
  2. カーソル移動
  3. ‘scrolloff’分だけ行を開ける
  4. 表示領域がずれて、マウスカーソルが指していた場所にあった単語も移動してしまう
  5. ダブルクリック (クリック2回目)
  6. 検索するつもりじゃなかった単語を検索してしまう
  7. 一瞬何が起こったのか分からない
  8. 理解し悲しみに暮れる

ということになり非常に悲しいです。 そして「9.Vimを説得する」というのがこの記事を書くに至るまでの過程でした。 ちなみに説得というのはEmacsで言うsetqとかけています。

'scrolloff'の再実装 〜yak shaving〜

これを解決するには、つまりシングルクリック(<LeftMouse>)時の移動を抑制すればいい訳です。 しかし'scrolloff'は設定しておきたい。 ならば'scrolloff'がやることをVimスクリプトで再実装して制御可能にし、一時的に無効にすればいい訳です。 以下がその'scrolloff'の実装です。 'scrolloff'オプションの代わりにg:scrolloffグローバル変数で指定するようになっています。

set scrolloff=0
let g:scrolloff = 15

" Hack for <LeftMouse> not to adjust ('scrolloff') when single-clicking.
" Implement 'scrolloff' by auto-command to control the fire.
autocmd vimrc CursorMoved * call s:reinventing_scrolloff()
let s:last_lnum = -1
function! s:reinventing_scrolloff()
    if s:last_lnum > 0 && line('.') ==# s:last_lnum
        return
    endif
    let s:last_lnum = line('.')
    let winline     = winline()
    let winheight   = winheight(0)
    let middle      = winheight / 2
    let upside      = (winheight / winline) >= 2
    " If upside is true, add winlines to above the cursor.
    " If upside is false, add winlines to under the cursor.
    if upside
        let up_num = g:scrolloff - winline + 1
        let up_num = winline + up_num > middle ? middle - winline : up_num
        if up_num > 0
            execute 'normal!' up_num."\<C-y>"
        endif
    else
        let down_num = g:scrolloff - (winheight - winline)
        let down_num = winline - down_num < middle ? winline - middle : down_num
        if down_num > 0
            execute 'normal!' down_num."\<C-e>"
        endif
    endif
endfunction

そしてこれは:autocmdを使って実装されているので 以下のように'eventignore'オプションという Vimスクリプトを書いているような人しか知らないオプションを使って 一時的に無効にできます。

nnoremap <silent> <LeftMouse> <Esc>:set eventignore=all<CR><LeftMouse>:set eventignore=<CR>

visualstar.vim

ここまででも十分ですが、マウスで選択し、クリックで検索できたらどんなに良い事でしょうか。 visualstar.vimを使えばそれができます。

vmap <LeftMouse> <Plug>(visualstar-g*)

まとめ

ここまでのまとめとして、以下を.vimrcに貼り付ければ完了です。

set scrolloff=0
let g:scrolloff = 15

" Hack for <LeftMouse> not to adjust ('scrolloff') when single-clicking.
" Implement 'scrolloff' by auto-command to control the fire.
autocmd vimrc CursorMoved * call s:reinventing_scrolloff()
let s:last_lnum = -1
function! s:reinventing_scrolloff()
    if s:last_lnum > 0 && line('.') ==# s:last_lnum
        return
    endif
    let s:last_lnum = line('.')
    let winline     = winline()
    let winheight   = winheight(0)
    let middle      = winheight / 2
    let upside      = (winheight / winline) >= 2
    " If upside is true, add winlines to above the cursor.
    " If upside is false, add winlines to under the cursor.
    if upside
        let up_num = g:scrolloff - winline + 1
        let up_num = winline + up_num > middle ? middle - winline : up_num
        if up_num > 0
            execute 'normal!' up_num."\<C-y>"
        endif
    else
        let down_num = g:scrolloff - (winheight - winline)
        let down_num = winline - down_num < middle ? winline - middle : down_num
        if down_num > 0
            execute 'normal!' down_num."\<C-e>"
        endif
    endif
endfunction

" Do not adjust current scroll position (do not fire 'scrolloff') on single-click.
nnoremap <silent> <LeftMouse>   <Esc>:set eventignore=all<CR><LeftMouse>:set eventignore=<CR>
" Double-click for searching the word under the cursor.
nnoremap          <2-LeftMouse> g*
" Single-click for searching the word selected in visual-mode.
vmap              <LeftMouse> <Plug>(visualstar-g*)
tyru

もどる
blog comments powered by Disqus