" sleuth.vim - Heuristically set buffer options " Maintainer: Tim Pope " 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(' ', 8) 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 = 8 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 if !exists('g:did_indent_on') filetype indent on endif 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() " vim:set et sw=2: