Vim で現在カーソルがある行と同じインデントを強調表示
最近ちょっと Vim で python を読む機会があったのだけど、ぱっと見てインデントが同じ行がどこまで分からず読み辛かった。
そんなわけで、現在カーソルがある行と同じ(もしくはそれ以上の)インデントレベルのところを強調表示する方法を模索したのでそのメモ
方法1: vim-indent-guides を使う
インデントを強調表示する、で検索すると出てきたのがこれ。
パッと使った感じ、以下の機能があった
- 等間隔で列を強調表示可能
- 色は自由に指定可能
多分もっと高機能っぽいけど、今回はこんなにおおげさな機能はいらなかった(というかデフォルトのままだと情報量が多すぎる。カスタマイズするのは面倒だった)のでパス
方法2: 適宜 highlight パターンを作る
今回とったのはこっち。
現在カーソルがある行の文字列を取得して、highlight 対象とするパターンを返却する簡単な関数を作った。
以下その関数
function! s:indent_pattern() let l:line = getline('.') let l:indent = matchstr(l:line, '^\zs\s\+\ze\S') let l:len = len(l:indent) - 1 if l:len > 0 return '^\s\{'. l:len . '}\zs\s\ze' else return '^\zs\s\ze' endif endfunction
# これだとスペースとタブが入り混じってると変な highlight になるけど気にしない。そんなコード書く方が悪い。
この関数から取得したパターンを matchadd で適当な highlight に追加してやればやりたいことができるはず。
matchadd とか matchdel する仕組みについては、ここ何回か同じような仕組みでほげほげしてきたので、ある程度汎用的に使えるようにした。
以下そのコード
" defined highlight sources let g:auto_highlights = {} " control enable/disable auto highlighting let g:enable_auto_highlight = 1 " disable auto highlight when filetype in below let g:auto_highlight_disable_file_type = ['unite', 'tmp'] function! s:compare_source(i1, i2) return a:i1.priority == a:i2.priority ? 0 : a:i1.priority > a:i2.priority ? 1 : -1 endfunction function! s:init_window_auto_highlight() let l:info = {} for highlight_kind in keys(g:auto_highlights) let l:info[highlight_kind] ={ \ 'current_match_pattern' : '', \ 'current_match_id' : -1, \} endfor return l:info endfunction " function for add highlight source function! s:define_auto_highlight_source(src) if !has_key(g:auto_highlights, a:src.highlight) let g:auto_highlights[a:src.highlight] = { \ 'sources' : [], \} endif call add(g:auto_highlights[a:src.highlight].sources, a:src) call sort(g:auto_highlights[a:src.highlight].sources, 's:compare_source') endfunction " main function! s:start_highlight() if !get(g:, "enable_auto_highlight", 0) || index(get(g:, 'auto_highlight_disable_file_type', []), &filetype) >= 0 return endif if !exists('w:auto_highlight_info') let w:auto_highlight_info = s:init_window_auto_highlight() endif for [item, value] in items(g:auto_highlights) for src in value.sources let l:pattern = src.pattern() if !empty(l:pattern) let l:target = w:auto_highlight_info[item] if l:target['current_match_pattern'] != l:pattern call s:ClearHighlight(item) let l:target['current_match_pattern'] = l:pattern let l:target['current_match_id'] = matchadd(item, l:pattern, src.priority) endif break endif endfor endfor endfunction " clear before highlight function! s:ClearHighlight(kind) if exists('w:auto_highlight_info') if w:auto_highlight_info[a:kind].current_match_id >= 0 call matchdelete(w:auto_highlight_info[a:kind].current_match_id) let w:auto_highlight_info[a:kind].current_match_id = -1 let w:auto_highlight_info[a:kind].current_match_pattern = '' endif endif endfunction " toggle enable/disable auto highlight function! s:toggle_auto_highlight() let g:enable_auto_highlight = !get(g:, "enable_auto_highlight", 0) call s:CheckEnableHighlightCurrentWord() endfunction function! s:CheckEnableHighlightCurrentWord() if !g:enable_auto_highlight for kind in keys(g:auto_highlights) call s:ClearHighlight(kind) endfor endif endfunction command! -bar ToggleCurrentHighlight call s:toggle_auto_highlight() command! -bar CurrentHighlight call s:start_highlight()
後、適当な autocmd に CurrentHighlight を実行するように設定してやる。
で、これまで作成した highlight を登録
" current word highlight let s:cword_highlight = { \ 'name': 'current_word', \ 'highlight': 'CurrentWord', \ 'priority': 10, \} function! s:EscapeText( text ) return substitute( escape(a:text, '\' . '^$.*[~'), "\n", '\\n', 'ge' ) endfunction function! s:cword_highlight.pattern() let l:cwd = expand('<cword>') if empty(l:cwd) return '' else let l:regexp = s:EscapeText(l:cwd) return l:cwd =~# '^\k\+$' ? '\<' . l:regexp . '\>' : l:regexp endif endfunction call s:define_auto_highlight_source(s:cword_highlight) " matchit highlight let s:matchit_highlight = { \ 'name': 'matchit', \ 'highlight': 'CurrentWord', \ 'priority': 9, \} function! s:InitMatchit() if !exists('b:match_words') return endif let l:mw = filter(split(b:match_words, ',\|:'), 'v:val !~ "^[(){}[\\]]$"') let b:reserved_regexp = join(l:mw, '\|') let mwre = '\%(' . b:reserved_regexp . '\)' let b:mwre = substitute(mwre, "'", "''", 'g') endfunction function! s:matchit_highlight.pattern() if !exists('b:match_words') return '' endif if !exists('b:reserved_regexp') call s:InitMatchit() endif if expand("<cword>") !~ b:reserved_regexp || empty(b:reserved_regexp) return '' endif let lcs = [] let wsv = winsaveview() while 1 exe 'normal %' let lc = {'line': line('.'), 'col': col('.')} if len(lcs) > 0 && (lcs[0] == lc || lcs[-1] == lc) break endif call add(lcs, lc) endwhile call winrestview(wsv) call map(lcs, '"\\%" . v:val.line . "l\\%" . v:val.col . "c"') let lcre = join(lcs, '\|') " final \& part of the regexp is a hack to improve html return '.*\%(' . lcre . '\).*\&' . b:mwre "return '.*\%(' . lcre . '\).*\&' . b:mwre . '\&\% (<\_[^>]\+>\|.*\)' endfunction call s:define_auto_highlight_source(s:matchit_highlight) " indent highlight let s:indent_highlight = { \ 'name': 'indent', \ 'highlight': 'Indent', \ 'priority': 10, \} function! s:indent_highlight.pattern() let l:line = getline('.') let l:indent = matchstr(l:line, '^\zs\s\+\ze\S') let l:len = len(l:indent) - 1 if l:len > 0 return '^\s\{'. l:len . '}\zs\s\ze' else return '^\zs\s\ze' endif endfunction call s:define_auto_highlight_source(s:indent_highlight)
作っておいて何だけど多分これ以上 source 増やさない。あんまり情報量増えても害なので…
以下あったら嬉しいかもしれない機能
- 変数の上にカーソルがあるとき、その変数の定義部分 or 代入部分だけ色を変える