本記事の実施環境
- Ubuntu 20.04 LTS 日本語 Remix(AlterLinux上のVirtualBox)
- NVIM v0.4.3
以前私はWindowsで競プロに必要なNeovim環境を整えるという記事を書いたが、結局の所Windowsで開発を行うのはかなり不利である。パッケージ管理ツールがまとものに整備されていなかったり、ドライブレターから始まる特殊なパスを使用していたりしていて、開発の上でWindowsを使うと予期せぬ自体がよく発生して大変不便だ。特にVimを使う場合はLinuxを使うべきだ。
私は現在Windows10とAlterLinux(ArchLinuxの派生OS)をデュアルブートして開発には主にAlterを使っているが、Linuxに移ったことでかなり開発環境の構築が楽になった。最近ではWSL2も動作可能になり、Windows上で簡単にLinuxが使えるようになったのでWindowsユーザーの方もWSL等のLinux環境を開発の中心にすることをおすすめする。
本記事では私が普段使っているAlterでの設定を参考にしつつ、最もよく使われているLinuxディストリビューションであるUbuntuで動作確認を行いながら書くことにする。
また、Neovimを前提とした書き方で記事が作られているが、本記事で紹介していることはVimもNeovimも変わらないものである。本家Vimを使用している場合は~/.config/nvim/init.vim
や$MYVIMRC
を~/.vimrc
と読み替えるだけでほぼすべての設定が使えるはずだ。しかし動作確認は行っていないのでできればNeovimを使うことを推奨する。
Vim、Neovimを使う意義についてはなぜ私はVimを使うのかで書いたのでNeoへの以降を考えるきっかけなどにしてほしい。
init.vimとは
init.vim
は、Neovim起動時に最初に読まれるスクリプトだ。
Vimにはコマンドがたくさんあり、基本的には:
でコマンドモードに入ってコマンドを打つことで実行できる。Vimではエディタの見た目や挙動を設定するのもすべてコマンドでできているが、毎回最初にそれをすべて手作業で実行するのは馬鹿らしい作業だ。そこで最初にすべて実行しても支障のないようなコマンドをinit.vim
に書き連ねることでその手間を省くことができる。さらにinit.vim
にはVim sciriptという言語が使えるので、条件分岐を使ったりコマンドを新たに追加することもできる。プラグインはinit.vim
に書いているようなスクリプトを別の場所に書いて読み込ませているようなものなので、一からinit.vim
に書くと大変なinit.vim
をまとめて引用しているようなものと言える。
NeoのつかないVimで.vimrc
というファイルに書いていたものと同じ感じで、書き方も変わらない。
場所はLinuxでは~/.config/nvim/init.vim
、WindowsではC:\Users\[ユーザー名]\AppData\Local\nvim\init.vim
だ。インストール時に自動で作られるわけではないので作っておこう。
cd ~/.config
mkdir nvim && cd nvim
touch init.vim
これでinit.vim
が作られた。一度init.vim
が作られるとNeovimの起動時に読み込まれて環境変数$MYVIMRC
にパスが代入されるので、以後はNeovimを開いた状態で:e $MYVIMRC
を実行するとinit.vim
のフルパスを覚えてなくても簡単に開くことができる。
本記事で説明すること
- 変数について
- setコマンド
- mapコマンド
- leaderキー
- normalコマンド
- functionの定義とcall
- autocmd
前提とすること
- Vimの基礎的な知識
- 一般的なプログラミング言語の知識
変数について
※変数は簡単なinit.vim
を書くのには必要ないので読み飛ばしても良い
Vim scriptも他のプログラミング言語と同様に変数を定義することができる。使える型は整数型、小数型、文字列型、リスト型、辞書型であり、だいたいPython等のスクリプト言語と使い方は同じである。しかしVim scriptは変数への代入の書き方に癖がある。
Vim scriptではlet [変数名] = [初期化する値]
で変数への代入を行う。ここで注意したいのは、let
はJavaScriptのvar
のような宣言のための識別子ではないということである。Vim scriptではlet
は変数に代入するコマンドであるので、2回目以降も代入するときには必ずlet
を使う。let
はコマンドである。
【例】
let a = 2 "aに2を代入
echo a "echoは出力のコマンド 2が出力される
let a = 3 "aに3を代入
echo a "3が出力される
実は上の書き方はあまり明確ではない。Vim scriptの変数はスコープ(有効範囲)を決めることができる。例えば、let s:script_value = 1
と宣言すると、そのスクリプト内でのみ有効な変数として使うことができる。以下は、変数名の前につけるものとスコープの表である。
変数の前 | スコープ |
---|---|
g: | グローバル |
l: | ローカル(関数内) |
s: | ローカル(スクリプト内) |
b: | ローカル(バッファ内) |
w: | ローカル(ウィンドウ内) |
t: | ローカル(タブページ内) |
a: | 関数の引数(後述) |
これを省略すると変数を作成した場所が関数内なら関数内、外ではグローバルで有効な変数になるようだが、自分で変数を定義する際には明示すると安心だ。
setコマンド
Vimにはオプションと呼ばれる値が存在している。オプションには切り替えオプション、数値オプション、文字列オプションの3つが存在している。setコマンドではオプションを変えたり、表示したりすることができる。
切り替えオプションはset [オプション名]
でONにでき、数値オプションと文字列オプションはset [オプション名]=[値]
で変更したり、複数の値をセットするような文字列オプションではset [オプション名]+=[値]
で追加したりできる。
また、init.vim
に書くよりコマンドモードで直接使う時が多いが、set [オプション名]?
で現在の値を確認できる。
ちなみに一文字しか変わらないがset
の代わりにse
でも使える。
また、setlocal
を使うと、そのバッファのみでオプションを変更することができるが、init.vim
に素で書くことはないだろう。これに関してはautocmd
と一緒に説明する。
Vimにはおびただしい量のオプションが存在するので全部は紹介できないが、私が設定しているオプションを以下に載せておく。必要な設定は調べればたいてい出てくるので、「これが足りない」と思ったら各自調べてみてほしい。
"行番号を表示(切り替え)
set number
"タブ文字の代わりにスペースを使う(切り替え)
set expandtab
"プログラミング言語に合わせて適切にインデントを自動挿入(切り替え)
set smartindent
"各コマンドやsmartindentで挿入する空白の量(数値)
set shiftwidth=4
"Tabキーで挿入するスペースの数(数値)
set softtabstop=4
"カレントディレクトリを自動で移動
set autochdir
"バッファ内で扱う文字コード(文字列)
set encoding=utf-8
"書き込む文字コード(文字列) : この場合encodingと同じなので省略可
set fileencoding=utf-8
"読み込む文字コード(文字列のリスト) : この場合UTF-8を試し、だめならShift_JIS
set fileencodings=utf-8,cp932
"Vimの無名レジスタとシステムのクリップボードを連携(文字列のリスト) : ダメならxclipをインストールで使えるかも
set clipboard+=unnamed,unnamedplus
"eコマンド等でTabキーを押すとパスを保管する(文字列のリスト) : この場合まず最長一致文字列まで補完し、2回目以降は一つづつ試す
set wildmode=longest,full
mapコマンド
map
コマンドは、好きな操作を好きなキーに登録する無限の可能性を秘めたコマンドである。init.vim
でどのようなmap
をするかがVimの設定で最も個性が出るところだと思う。基本的な使い方はmap [実際に押すキー] [押したことにするキー]
である。Ctrlキー、Shiftキー、Altキーとの同時押しはそれぞれ<C-a>
、<S-a>
、<A-a>
と書いたり、Enterキーは<CR>
、Escキーは<Esc>
と書いたりするので、わからなかったら調べるといい。例えば、
map <C-j> G
と書くと、Ctrlキーとjキーを同時押ししたときに変わりにG
(Shift+g
)を押したことになる(つまり最終行に飛ぶ)。ここで、g
とG
は区別することに注意する。もちろん例として作ったこのマップに実用性はないだろう。
map
コマンドは、ノーマルモードとビジュアルモード、選択モード、オペレータ待機中で有効だが、適応させるモードによってコマンドも少し異なる。どのモードに対応するかは以下の表のようになる。
コマンド | モード |
---|---|
map | ノーマルモード、ビジュアルモード、選択モード、オペレータ待機中 |
map! | 挿入モード、コマンドモード |
nmap | ノーマルモード |
imap | 挿入モード |
cmap | コマンドモード |
vmap | ビジュアルモードと選択モード |
xmap | ビジュアルモード |
smap | 選択モード |
omap | オペレータ待機中 |
lmap | 挿入モード、コマンドモード、Lang-Arg |
tmap | ターミナルモード |
知らない状態が入っているかもしれないが、ここでは本質的でないため説明は省略する。
noremap
コマンドは、他のmap
に上書きされないコマンドである。map
を使うと、map
によって仮想的に押されたキーが別のmap
や別のキーバインドに反応してしまうが、noremap
ではそれを防ぐことができる。また、nnoremap
がノーマルモードだけを対象にするように、map
の前につけたアルファベット一文字をnoremap
の前につけることができる。
さらに、map
などのコマンドには<expr>
、<silent>
などの特別な引数をつけることができる。他にもいくつかあるようだが、<expr>
と<silent>
くらいしか使わないのでここではその2つに絞って説明する。
map
の引数に<expr>
を入れると、マッピング先が式として評価される。つまりmap <expr> [キー] [式]
で、Vim scriptで書いた式を計算した結果の文字列をそのままキーバイドとして打ったことにするということだ。わかりにくいので以下に例を示す。
nnoremap <expr> S* ':%s/\<' . expand('<cword>') . '\>/'
これは例としては少し複雑であるが、実際に私が使用しているキーマップである。順を追って説明する。
まず、expand('<cword>')
はカーソル直下の単語を文字列として返す関数である。
次に、.
は文字列の結合を意味する。多くのプログラミング言語では+
などが使われているがVim scriptでは.
である。
よって、例えばカーソルの下にhoge
という単語がある時、':%s/\<' . expand('<cword>') . '\>/'
は':%s/\<hoge\>/'
と展開される。
つまりこのキーバインドはS*
と打ったときに変わりに:%s/\<hoge\>/
というようにキーを打ったことにするというものだ。
ちなみに:%s/置換前/置換後/g
は置換を行うコマンドで、\<
は単語の先頭、\>
は単語の末尾にマッチするメタ文字である。
結果的にこのマップは、S*
を押すとカーソル下の単語と同じ単語を別の単語に置換するためのコマンドを書きかける、というものである。
最後に、<silent>
を説明する。これはそのマップによる操作ではコマンドラインに実行したコマンドを表示しないというものである。文章だけでは難しいので以下に例を載せる。これも私が実際に使っているマップである。
inoremap <silent> <Esc> <Esc>:call system('fcitx-remote -c')<CR>
まず、このキーマップの使用目的を説明する。
Vimで日本語を入力したあとEsc
キーを押してノーマルモードにもどったとき、日本語入力メソッドがONのままでVimキーバインドが打てなくてイライラすることがある。このキーマップは挿入モードからEsc
を押したときに自動的に入力メソッドをOFFにするためのものである。
まず、Esc
を送信して通常通りノーマルモードに戻る。
次に入力メソッドをOFFにするためにシステムコマンド(fcitx-remote -c
)を実行する。関数については後で解説するが、system()
はシステムコマンドを実行する関数である。
さらに、call
コマンドは、関数を実行するコマンドであるので、これによってsystem('fcitx-remote -c')
という関数をコマンドから実行する。
全体として、ノーマルモードに戻った後入力メソッドを切るということになるのである。
さて、本題であるが、<silent>
はここでどのような役割を担っているか、もし<silent>
がなかったら、ノーマルモードに移行するたびに:call system('fcitx-remote -c')
というメッセージがコマンドラインに残って邪魔である。そこでこのキーマップに<silent>
をつけておくと、何も表示せずにコマンドを実行してくれるので、通常のEsc
を使っているかのように実行することができる。
なおfcitx-remote -c
はUbuntuではデフォルトでは使えないのでsudo apt install fcitx fcitx-mozc
でインストールした後設定で切り替え方法をfcitx
の変えるか、他の使えるコマンドを利用すると良い。
余談だが、このキーマップはLinux向きである。Windowsでこの機能を実装するにはpepo-le/win-ime-conというプラグインをインストールするのが賢明だ。
<silent>
は個人的にそれほど重要ではないと思っているので無理に覚える必要はないと思う。
leaderキー
map
コマンドについて説明したが、実は多くのキーバインドがVimによって定義されていて、どのキーバインドともかぶらないものを設定するというのは非常に難しい。そこで登場するのがleader
キーである。leader
を使うキーバインドはデフォルトでは登録されておらず、好きなように使うことができる。とはいっても本来キーボードにleader
なんてキーは存在しないが、Vimでは仮想的にそういうキーがセットされており、好きなキーをleader
キーとして使うことができる。だいたいデフォルトでは\
(バックスラッシュ)に当てられている。好きなキーと言っても大体のキーはleader
以外のキーバインドで使っているので多くの場合はSpace
キーや,
キーに割り当てられる。例として、Space
キーに割り当てる。
let mapleader = "\<Space>"
leader
キーはmapleaderという変数に登録されていて、Vimではキーも文字列として管理されている。""
で囲った文字列の中でバックスラッシュによってエスケープすることで、<Space>
を文字列として扱うことができる。
normalコマンド
normal
コマンドはその名が表すように大変シンプルなコマンドである。normal
コマンドは、引数に取った文字列のとおりにキーを押したことにするコマンドだ。コマンドにもなっていないような単純な操作を一括でしたいときにかゆいときに手が届く。
ifコマンド
Vim scriptでは条件分岐もコマンドになっている。そう入っても書き方は割と普通である。
if 条件式
"ここに必要な処理
elseif 条件式
"ここに必要な処理
else
"ここに必要な処理
endif
ここで注意したいのは、Vim scriptには真偽値型がないということである。だからif
コマンドで評価した条件式は数値型に自動置換され、0
以外なら真、0
なら偽として進めることになっている。実際には条件演算子が結果に対応した数値を返すからあまり気にする必要はない。
functionの定義とcall
長めの処理を関数として定義する。関数は基本的には以下のように定義する。
function! 関数名(引数1, 引数2)
"ここに必要な処理
return 戻り値
endfunction
function
のあとの!
はその関数が定義済みだった場合上書きしてこれにするという意味である。プラグイン等でも関数が定義されるので、知らない関数が存在していたときのためにつけるのが一般的なようだ。戻り地が必要なければreturn
コマンドを入れる必要はない。
また、関数名は組み込み関数と区別するために大文字で始める必要がある。
コマンドを自己定義することもできる。これも同じように大文字で始める。
関数は当然式の中に関数名(引数1,引数2)
の形で呼び出して戻り値を使うことができるが、文として使いたいときはcall
コマンドを使う。
function! Myterm()
split
wincmd j
resize 10
terminal
wincmd k
endfunction
command! Myterm call Myterm()
if has('vim_starting')
call Myterm()
endif
これは、画面を上下に分割して下側に高さ10のターミナルを開くための関数Myterm()
を定義した例である。
また、command!
で始まる行によってこの関数をコマンドとしても呼び出せるようにしている。
さらに、その下では、if
コマンドを使ってVimの起動時にMyterm()
を自動実行するための文である。init.vim
は起動時に自動で読み込まれ、その間vim_starting
というステータスが有効になる。これを踏まえて、if
コマンドとhas()
関数を使って起動時の読み込みでのみターミナルを開く用にしているというわけだ。
autocmd
init.vim
に書いたスクリプトは読み込まれたときに即座に実行されるが、autocmd
を使うことで特定のタイミングに自動実行することができる。
書き方はautocmd [イベント] [イベントの引数] [コマンド]
といった感じである。イベントはたくさん存在するが、ドキュメントを参照して自分で確認してほしい。
以下は、filetype
オプションがjavascript
のとき、インデントの幅を特別に2にする設定である。
autocmd FileType javascript set shiftwidth=4 softtabstop=4
FileType
イベントは、filetype
オプションが設定されたときに発生するイベントで、それがjavascript
に等しいときはset shiftwidth=4 softtabstop=4
を実行する。
筆者の実際のinit.vim
最後に、私のinit.vim
の一部を公開しておく。どんな設定をすればいいかわからない人は参考にしてほしい。
"行番号を表示
set number
"タブ文字の代わりにスペースを使う
set expandtab
"プログラミング言語に合わせて適切にインデントを自動挿入
set smartindent
"各コマンドやsmartindentで挿入する空白の量
set shiftwidth=4
"Tabキーで挿入するスペースの数
set softtabstop=4
"カレントディレクトリを自動で移動
set autochdir
"バッファ内で扱う文字コード
set encoding=utf-8
"書き込む文字コード : この場合encodingと同じなので省略可
set fileencoding=utf-8
"読み込む文字コード : この場合UTF-8を試し、だめならShift_JIS
set fileencodings=utf-8,cp932
"Vimの無名レジスタとシステムのクリップボードを連携 : ダメならxclipをインストールで使えるかも
set clipboard+=unnamed,unnamedplus
"eコマンド等でTabキーを押すとパスを保管する : この場合まず最長一致文字列まで補完し、2回目以降は一つづつ試す
set wildmode=longest,full
"LeaderキーをSpaceに設定(これだけでは意味をなさない)
let mapleader = "\<Space>"
"C++,Java等のインラインブロックを中括弧付きのブロックに展開
nnoremap <C-j> ^/(<CR>%a{<CR><Esc>o}<Esc>
"カーソル上の単語を置換
nnoremap <expr> S* ':%s/\<' . expand('<cword>') . '\>/'
"挿入モード終了時にIMEをオフ
inoremap <silent> <Esc> <Esc>:call system('fcitx-remote -c')<CR>
"下部分にターミナルウィンドウを作る
function! Myterm()
split
wincmd j
resize 10
terminal
wincmd k
endfunction
command! Myterm call Myterm()
"起動時にターミナルウィンドウを設置
if has('vim_starting')
Myterm
endif
"上のエディタウィンドウと下のターミナルウィンドウ(ターミナル挿入モード)を行き来
tnoremap <C-t> <C-\><C-n><C-w>k
nnoremap <C-t> <C-w>ji
"ターミナル挿入モードからターミナルモードへ以降
tnoremap <Esc> <C-\><C-n>
"ファイルタイプごとにコンパイル/実行コマンドを定義
function! Setup()
"フルパスから拡張子を除いたもの
let l:no_ext_path = printf("%s/%s", expand("%:h"), expand("%:r"))
"各言語の実行コマンド
let g:compile_command_dict = {
\'c': printf('gcc -std=gnu11 -O2 -lm -o %s.out %s && %s/%s.out', expand("%:r"), expand("%:p"), expand("%:h"), expand("%:r")),
\'cpp': printf('g++ -std=gnu++17 -O2 -o %s.out %s && %s/%s.out', expand("%:r"), expand("%:p"), expand("%:h"), expand("%:r")),
\'java': printf('javac %s && java %s', expand("%:p"), expand("%:r")),
\'cs': printf('mcs -r:System.Numerics -langversion:latest %s && mono %s/%s.exe', expand("%:p"), expand("%:h"), expand("%:r")),
\'python': printf('python3 %s', expand("%:p")),
\'ruby': printf('ruby %s', expand("%:p")),
\'javascript': printf('node %s', expand("%:p")),
\'sh': printf('chmod u+x %s && %s', expand("%:p"), expand("%:p"))
\}
"実行コマンド辞書に入ってたら実行キーバインドを設定
if match(keys(g:compile_command_dict), &filetype) >= 0
"下ウィンドウがターミナルであることを前提としている
nnoremap <expr> <F5> '<C-w>ji<C-u>' . g:compile_command_dict[&filetype] . '<CR>'
endif
endfunction
command! Setup call Setup()
"ファイルを開き直したときに実行コマンドを再設定
autocmd BufNewFile,BufRead * Setup
"RubyとJSではインデントを2マスにする
autocmd FileType ruby,javascript set shiftwidth=2 softtabstop=2
これが、私が実際に使っているinit.vim
のうち、プラグインや自作ツールが絡まない部分である。私はまだこれで完全に満足しているわけではないので、これからもっと快適なVimを作り上げていきたいと思っている。
init.vim
は本当に個性の出るファイルなのでみなさんもぜひ最高の設定を目指して作ってみてほしい。
最後に
わからなかったらヘルプ機能を使おう。:h [知りたい単語]
でだいたい出てくる。
参考
Vim日本語ドキュメント
Vimスクリプト基礎文法最速マスター - 永遠に未完成
vimrc基礎文法最速マスター - 永遠に未完成