Vimのグローバルコマンドでコード編集を高速化

はじめに

こんにちは! イタンジ株式会社で更新退去くんというプロダクトを開発している佐藤です。

最近Vimの魅力に取り憑かれてしまった一人です。
約3ヶ月前にVimに手を出して、もう後戻りできないくらい夢中になっています🤩
(VScode1年、RubyMine1年を経て、Vimに辿り着きました🎉)

Vimは毎日新しい発見があり、とても面白いです!
特に、最近「グローバルコマンド(global command)」なるものを発見! 少し触ってみたところ、勉強しがいがあるかもと思いました!

今日はこのグローバルコマンドを一緒に勉強して行けたらなと思います。

グローバルコマンドって何?

Vimにはたくさんの機能がありますが、今日はその中でも「グローバルコマンド」に焦点を当てます。
このコマンドは、特定のパターンにマッチする行に対して一括でコマンドを適用するものです。

基本的な使い方

基本的な使い方は非常にシンプルです。:g で始め、その後にパターンとコマンドを指定します。

:g:g! の基本構文

  • :g/pattern/commandpatternにマッチする行にcommandを適用
  • :g!/pattern/commandpatternにマッチしない行にcommandを適用

pattern と command

patternは正規表現で指定でき、commandはVimの各種コマンド(例:dで削除、sで置換)です。

実際の例

  • :g/foo/dfooという文字列を含む全ての行を削除
  • :g/^#/normal dd#で始まる行をddコマンド(行削除)で削除

実用例:各種コマンドを使ったテクニック

削除(Delete)

変更前

# TODO: Remove this method
def hello
  puts "Hello, World!"
end

コマンド

:g/TODO/d

変更後

def hello
  puts "Hello, World!"
end

解説

このコマンドで、TODO を含むコメント行が削除されます。

置換(Substitute)

変更前

<div className="oldClass">Content1</div>
<span className="oldClass">Content2</span>
<div className="oldClass">Content3</div>

コマンド

:g/<div className="oldClass"/s/oldClass/newClass/g

変更後

<div className="newClass">Content1</div>
<span className="oldClass">Content2</span>
<div className="newClass">Content3</div>

解説

一般的には、:s(または:%s)コマンドで置換が可能です。 しかし、:gと組み合わせることで、特定のパターンにマッチする行だけに置換を適用する操作が可能になります。

このコマンドで、<div className="oldClass"> というパターンにマッチする行のみ、oldClassnewClass に置換されます。

ノーマルモードコマンド(Normal)

変更前

const x = 10;
const y = 'hello';

コマンド

:g/const/normal A // eslint-disable-line

変更後

const x = 10; // eslint-disable-line
const y = 'hello'; // eslint-disable-line

解説

:normal コマンドを使うことで、ノーマルモードのコマンドをExコマンド内で実行できます。
(Exコマンドは、コマンドラインモード(":")で使用されるコマンド群です)
(今回はgというExコマンド内で使うという文脈で使用している)

この例では、const を含む各行の末尾に // eslint-disable-line というESLintを無効にするコメントを追加しています。

表示(Print)

変更前

const Button = () => {
  return <button className="primary">Click Me!</button>;
};

const Alert = () => {
  return <div className="alert">Warning!</div>;
};

コマンド

:g/className/p

実行結果

  return <button className="primary">Click Me!</button>;
  return <div className="alert">Warning!</div>;

解説

:p コマンドは、マッチした行を表示するためのコマンドです。

この例では、className 属性を持つJSX要素が表示されます。

応用例: パターン範囲を使用する

パターン範囲とは?

Vimでは、特定の行だけでなく、行の範囲に対してもコマンドを適用することができます。 パターン範囲は、:/pattern1/,/pattern2/のように、2つのパターンで始点と終点を指定します。
(厳密には範囲の指定方法の一種なので、パターン範囲という名称があるわけではないです)
(ただ単にマッチした行を範囲の行指定子の対象にできるということです)

この範囲内のすべての行がコマンドの対象となります。

一般的な形式

:[range]command
  • range: 範囲。例::/begin/,/end/など。
  • command: 適用するコマンド。例:deleteなど

例: 複数行のコピー

変更前

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end

コマンド

:g/def show/,/end/t$-1

変更後

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
  def show
    @user = User.find(params[:id])
  end
end

解説

この例では、:t コマンドとアドレス範囲:g/def show/,/end/を組み合わせて、def showからendまでのブロックをファイルの末尾から1行上にコピーしています。

まとめ

globalコマンド便利ですね! 私自身、この記事を書きながら新しい発見がたくさんありました。
(恥ずかしながら、パターン範囲は記事を書いている最中に知りました🤯)

おかげで今回もVimmerとしてのランクアップを遂行することができました🫡

最後に一言

「お前もVimmerにならないか?」

参考資料

https://vim-jp.org/vimdoc-ja/cmdline.html
https://vim-jp.org/vimdoc-ja/cmdline.html#cmdline-ranges
https://vim-jp.org/vim-users-jp/2009/06/22/Hack-30.html
https://vim-jp.org/vimdoc-ja/repeat.html#:global