[VIM] まだインデントで消耗してるの? 〜スペースインデントでもタブインデントでもない新しいインデント手法〜

皆さんはスペースインデントとタブインデントのどちらをお使いでしょうか。
私はタブインデント派なのですが、タブインデントとスペースインデントの双方に利点が存在するため、簡単にどちらが良いと決めることはできません。
ここでは、タブインデントとスペースインデントを組み合わせた新しいインデントの方法を提案し、Vim上で実現するための`vimrc`の実装を紹介します。

※ 提案手法を実際に導入される場合、なにか問題が起こっても私は責任を負いかねますのでご了承ください[^3][^4][^5]。

[^3]: 得られるものの少なさの割にとても面倒なので定着しないと思う。
[^4]: 複数人で開発している場合、全員の理解が得られてから採用しましょう。
[^5]: タイトルに特に深い意味はありません。

# タブインデントの利点と欠点

1. ソースコードの閲覧者が`tabstop`の設定により見やすいインデント幅で閲覧できる
1. タブ幅が環境によってまちまち[^1]なので、ソースコードの縦位置がくずれる場合がある

ソースコード閲覧時のタブ幅は人によって好みが異なるので、それぞれの人が好きなタブ幅で閲覧できれば幸せになれますね。
 2.については、例えば以下を見てください。

```c
// タブ幅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`の中の条件式の先頭がずれてしまい、若干見苦しいです。

[^1]: Vimの`modeline`を用いて解決することもできます。しかしながらその場合は1.の利点を潰すことになります。


# 新しいインデント手法

このように、タブインデントには好みのインデント幅でソースコードを閲覧できるという利点がある反面、設定によっては文字の縦位置がそろわず表示が崩れてしまうという問題があります。
そこで提案したいのは、「通常はタブインデントだが、幅が変わると困る部分のみスペースでインデントする」という方法です。

例えば先程の例でこのようなインデントスタイルを用いると以下のようになります。

```c
// タブ幅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`より後の部分に)してください。

```vim:~/.vimrc
" 以下の設定はお好みで
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`)の位置までスペースインデントを挿入します[^2]。

[^2]: スペース直後に強制的にタブを入力したい場合は、挿入モードで`<C+v>`を押した後にタブキーを押します。