LoginSignup
9
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-10

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

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

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

  1. ソースコードの閲覧者がtabstopの設定により見やすいインデント幅で閲覧できる
  2. タブ幅が環境によってまちまち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より後の部分に)してください。

~/.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)の位置までスペースインデントを挿入します5


  1. 得られるものの少なさの割にとても面倒なので定着しないと思う。 

  2. 複数人で開発している場合、全員の理解が得られてから採用しましょう。 

  3. タイトルに特に深い意味はありません。 

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

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

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4