🏎️

VimでTextObjの末尾や先頭に移動する

2024/12/20に公開

何ができるようになるか?

ノーマルモードにてg + テキストオブジェクトで
テキストオブジェクトの先頭や末尾に移動できるようになります!
例えばテキストオブジェクトtはHTMLタグなので、
がカーソル位置としてgItで以下の様に移動できるようになります
これが

<div>
  foo
  bar
  ...
  baz█
</div>

↓こう

<div>
  █oo
  bar
  ...
  baz
</div>

%と違い任意のテキストオブジェクトを指定できるので便利!
…たぶん便利!

実装

こんな感じでいけます

  • テキストオブジェクトの末尾に移動

    function! s:ToTail(_)
      call getpos("']")->setpos('.')
    endfunction
    noremap <SID>(ToTail) <ScriptCmd>set operatorfunc=<SID>ToTail<CR>g@
    nmap ga <SID>(ToTail)a
    nmap gi <SID>(ToTail)i
    
  • テキストオブジェクトの先頭に移動

    function! s:ToHead(_)
      let p = getpos("'[")
      call setpos('.', p)
      if p[2] <= 1
        normal ^
      elseif getline(p[1])->len() <= p[2]
        normal j^
      endif
    endfunction
    noremap <SID>(ToHead) <ScriptCmd>set operatorfunc=<SID>ToHead<CR>g@
    nmap gA <SID>(ToHead)a
    nmap gI <SID>(ToHead)i
    

解説

gagiでテキストオブジェクトの末尾、gAgIで先頭に移動します

実装について

s:ToTail()

テキストオブジェクトの末尾にカーソルをセットしています
これだけで書けちゃうんですね

s:ToHead()

先頭への移動はちょっと微調整しています

  • インデントを無視したい場面が多かったのでnormal ^で位置調整してます
  • 同様にgIでの移動で行末に移動した場合も次の行の先頭へ位置調整してます

マッピングについて

テキストオブジェクトを扱うときはoperatorfuncを使いますが、単純に

noremap g <ScriptCmd>set operatorfunc=<SID>ToHead<CR>g@

としてしまうと、gで始まるマッピングが軒並使えなくなってしまいます!
なので、スクリプトローカルな<SID>(ToTail)<SID>(ToHead)を定義し回避しています

デフォルトのマッピングについて

  • gaは使う頻度は少ないと思いますが、残したいなら<Leader>gaとかに退避させましょう
  • gig;iで代用できます

他の案

]a]i[a[iにマッピングするのもありかと思います(自分は普段使いの40%キーボードとスマホで打ちづらいのでやりませんが)
GaGigagiにマッピングしてGGGに退避するのもありかもしれませんね🤔
Gを他のマッピングのprefixにも使えそうですし、ggに対してのGGも直感的な気がします

ビジュアルモードとオペレーターに対応したプラグイン化

このままだとノーマルモードでしか動かないので、
ビジュアルモードとオペレーターに対応してプラグイン化してみました

https://github.com/utubo/vim-headtail

こんな感じで動くようになると思います

call dein#add('utubo/vim-headtail')
HeadTailMap g G

追伸

今日12/20は私の誕生日です!わーい!🎂ฅ⊏'ꣲ'|ฅ


GitHubで編集を提案

Discussion