vim-sleuth/plugin/sleuth.vim

185 lines
4.4 KiB
VimL

" sleuth.vim - Heuristically set buffer options
" Maintainer: Tim Pope <http://tpo.pe/>
" Version: 1.1
" GetLatestVimScripts: 4375 1 :AutoInstall: sleuth.vim
if exists("g:loaded_sleuth") || v:version < 700 || &cp
finish
endif
let g:loaded_sleuth = 1
function! s:guess(lines) abort
let options = {}
let heuristics = {'spaces': 0, 'hard': 0, 'soft': 0}
let ccomment = 0
let podcomment = 0
let triplequote = 0
let backtick = 0
let softtab = repeat(' ', 4)
for line in a:lines
if !len(line) || line =~# '^\s*$'
continue
endif
if line =~# '^\s*/\*'
let ccomment = 1
endif
if ccomment
if line =~# '\*/'
let ccomment = 0
endif
continue
endif
if line =~# '^=\w'
let podcomment = 1
endif
if podcomment
if line =~# '^=\%(end\|cut\)\>'
let podcomment = 0
endif
continue
endif
if triplequote
if line =~# '^[^"]*"""[^"]*$'
let triplequote = 0
endif
continue
elseif line =~# '^[^"]*"""[^"]*$'
let triplequote = 1
endif
if backtick
if line =~# '^[^`]*`[^`]*$'
let backtick = 0
endif
continue
elseif line =~# '^[^`]*`[^`]*$'
let backtick = 1
endif
if line =~# '^\t'
let heuristics.hard += 1
elseif line =~# '^' . softtab
let heuristics.soft += 1
endif
if line =~# '^ '
let heuristics.spaces += 1
endif
let indent = len(matchstr(substitute(line, '\t', softtab, 'g'), '^ *'))
if indent > 1 && get(options, 'shiftwidth', 99) > indent
let options.shiftwidth = indent
endif
endfor
if heuristics.hard && !heuristics.spaces
return {'expandtab': 0, 'shiftwidth': &tabstop}
elseif heuristics.soft != heuristics.hard
let options.expandtab = heuristics.soft > heuristics.hard
if heuristics.hard
let options.tabstop = 4
endif
endif
return options
endfunction
function! s:patterns_for(type) abort
if a:type ==# ''
return []
endif
if !exists('s:patterns')
redir => capture
silent autocmd BufRead
redir END
let patterns = {
\ 'c': ['*.c'],
\ 'html': ['*.html'],
\ 'sh': ['*.sh'],
\ }
let setfpattern = '\s\+\%(setf\%[iletype]\s\+\|set\%[local]\s\+\%(ft\|filetype\)=\|call SetFileTypeSH(["'']\%(ba\|k\)\=\%(sh\)\@=\)'
for line in split(capture, "\n")
let match = matchlist(line, '^\s*\(\S\+\)\='.setfpattern.'\(\w\+\)')
if !empty(match)
call extend(patterns, {match[2]: []}, 'keep')
call extend(patterns[match[2]], [match[1] ==# '' ? last : match[1]])
endif
let last = matchstr(line, '\S.*')
endfor
let s:patterns = patterns
endif
return copy(get(s:patterns, a:type, []))
endfunction
function! s:apply_if_ready(options) abort
if !has_key(a:options, 'expandtab') || !has_key(a:options, 'shiftwidth')
return 0
else
for [option, value] in items(a:options)
call setbufvar('', '&'.option, value)
endfor
return 1
endif
endfunction
function! s:detect() abort
if &modifiable == 0
return
endif
let options = s:guess(getline(1, 1024))
if s:apply_if_ready(options)
return
endif
let patterns = s:patterns_for(&filetype)
call filter(patterns, 'v:val !~# "/"')
let dir = expand('%:p:h')
while isdirectory(dir) && dir !=# fnamemodify(dir, ':h')
for pattern in patterns
for neighbor in split(glob(dir.'/'.pattern), "\n")[0:7]
if neighbor !=# expand('%:p') && filereadable(neighbor)
call extend(options, s:guess(readfile(neighbor, '', 256)), 'keep')
endif
if s:apply_if_ready(options)
let b:sleuth_culprit = neighbor
return
endif
endfor
endfor
let dir = fnamemodify(dir, ':h')
endwhile
if has_key(options, 'shiftwidth')
return s:apply_if_ready(extend({'expandtab': 1}, options))
endif
endfunction
setglobal smarttab
function! SleuthIndicator() abort
if &expandtab
return 'sw='.&shiftwidth
elseif &tabstop == &shiftwidth
return 'ts='.&tabstop
else
return 'sw='.&shiftwidth.',ts='.&tabstop
endif
endfunction
augroup sleuth
autocmd!
autocmd FileType * if get(g:, 'sleuth_automatic', 1) | call s:detect() | endif
autocmd FileType * call s:detect()
autocmd User Flags call Hoist('buffer', 5, 'SleuthIndicator')
augroup END
command! -bar -bang Sleuth call s:detect()
if exists('g:did_indent_on')
filetype indent off
endif
filetype indent on
" vim:set et sw=2: