皆さんはスペースインデントとタブインデントのどちらをお使いでしょうか。
私はタブインデント派なのですが、タブインデントとスペースインデントの双方に利点が存在するため、簡単にどちらが良いと決めることはできません。
ここでは、タブインデントとスペースインデントを組み合わせた新しいインデントの方法を提案し、Vim上で実現するためのvimrc
の実装を紹介します。
※ 提案手法を実際に導入される場合、なにか問題が起こっても私は責任を負いかねますのでご了承ください123。
タブインデントの利点と欠点
- ソースコードの閲覧者が
tabstop
の設定により見やすいインデント幅で閲覧できる - タブ幅が環境によってまちまち4なので、ソースコードの縦位置がくずれる場合がある
ソースコード閲覧時のタブ幅は人によって好みが異なるので、それぞれの人が好きなタブ幅で閲覧できれば幸せになれますね。
2.については、例えば以下を見てください。
// タブ幅4のとき
if (left <= x && x <= right &&
top <= x && x <= bottom) { /* ... */ }
// 同じソースコードをtabstop=8で閲覧すると...。
if (left <= x && x <= right &&
top <= x && x <= bottom) { /* ... */ }
これは、タブ幅4のタブインデントを用いて書いたC言語ソースコードのイメージです。同じソースコードをタブ幅8の環境で閲覧するとif
の中の条件式の先頭がずれてしまい、若干見苦しいです。
新しいインデント手法
このように、タブインデントには好みのインデント幅でソースコードを閲覧できるという利点がある反面、設定によっては文字の縦位置がそろわず表示が崩れてしまうという問題があります。
そこで提案したいのは、「通常はタブインデントだが、幅が変わると困る部分のみスペースでインデントする」という方法です。
例えば先程の例でこのようなインデントスタイルを用いると以下のようになります。
// タブ幅4のとき
~~~~if (left <= x && x <= right &&
~~~~____top <= x && x <= bottom) { /* ... */ }
// 同じソースコードをtabstop=8で閲覧しても大丈夫
~~~~~~~~if (left <= x && x <= right &&
~~~~~~~~____top <= x && x <= bottom) { /* ... */ }
この例で~
はタブを、_
はスペースを表します。タブ幅が変わってもスペース部の幅は変わらないため表示が崩れていないことがわかります。
Vim上での実装
上で示したタブとスペースの使い分けを手動でするのは大変なので、簡単なVimscriptを書いてみました。
~/.vimrc
に追記(ftplugin
を使っている方は、filetype indent on
より後の部分に)してください。
" 以下の設定はお好みで
set noexpandtab
set autoindent
set smartindent
set tabstop=4
set shiftwidth=0
set list
set listchars=tab:»\ ,trail:-
highlight SpecialKey ctermfg=242
" 以下、本体
function! s:InsertSpaceIndent()
let sts = &softtabstop == 0 ? &tabstop : &softtabstop
let col = col('.') - 1
if col == 0 || getline('.')[col - 1] != ' '
return " "
else
let s = ""
let c = (virtcol('.') - 1) % sts
while c < sts
let s = s . " "
let c = c + 1
endwhile
return s
endif
endfunction
function! s:CopyIndent(r)
let ts = &tabstop
let l = line('.')
let r = a:r + l
let ls = getline(l)
let lm = strlen(ls)
let rs = getline(r)
let rm = strlen(rs)
echo ls
let c = 0
let d = 0
let i = 0
let j = 0
while i < lm
let lc = ls[i]
if lc == ' '
let c = c + 1
elseif lc == ' '
let c = c + ts - ( c % ts )
else
break
endif
let i = i + 1
endwhile
while j < rm && d < c
let rc = rs[j]
if rc == ' '
let d = d + 1
elseif rc == ' ' && d + ts - ( d % ts ) <= c
let d = d + ts - ( d % ts )
else
break
endif
let j = j + 1
endwhile
let rs = strpart(rs, 0, j)
while d < c
if &expandtab == 0 && d + ts - ( d % ts ) <= c
let rs = rs . ' '
let d = d + ts - ( d % ts )
else
let rs = rs . ' '
let d = d + 1
endif
endwhile
let rs = rs . strpart(ls, i)
call setline('.', rs)
endfunction
inoremap <expr> <C-i> <SID>InsertSpaceIndent()
inoremap <CR> <CR>a<BS><ESC>:call <SID>CopyIndent(-1)<CR>A
nnoremap o oa<BS><Esc>:call <SID>CopyIndent(-1)<CR>A
nnoremap O Oa<BS><Esc>:call <SID>CopyIndent(1)<CR>A
使い方
挿入モードにて、<Tab>
キーを押すとタブインデントを挿入します。
1つスペースを入れた直後で<Tab>
キーを押すと次のtabstop
(またはsofttabstop
)の位置までスペースインデントを挿入します5。