" Numeric comparison function to sort in a Vim v7.0 compatible way function! s:CompareNumeric(a, b) abort return a:a > a:b ? 1 : -1 endfunction " Entry point for plugin function! detect_indent#() abort " If there's no filetype, don't do anything if &filetype ==# '' return endif " If this is a special buffer, don't do anything if index(['nofile', 'quickfix', 'help'], &buftype) >= 0 return endif " For spaces, we count both the total space-indented lines, and also the " count of lines indexed by space count, so that if we need to, we can " figure out a good 'shiftwidth' setting; for tabs, we just count the " indented lines, since we won't need to set 'shiftwidth' for that. " let tabs = 0 let spaces = { \ 'hist': {}, \ 'total': 0, \} " Figure out how many lines we'll check; cap this to 1,024, or whatever the " user configured let total = max([line('$'), get(g:, 'detect_indent_limit', 1024)]) " Iterate through the lines for line in getline(1, total) " If there are leading tabs, we'll call this a tab-indented line; bump the " appropriate count, and skip the rest of the loop. " if matchstr(line, '^\t*') !=# '' let tabs += 1 continue endif " Figure out count of space indenting; skip to the next line if it's zero let indent = strlen(matchstr(line, '^ *')) if indent == 0 continue endif " Increment the count of space-indented lines let spaces['total'] += 1 " Create a dictionary entry in the histogram for this indent if necessary, " and increment that counter " if !has_key(spaces['hist'], indent) let spaces['hist'][indent] = 0 endif let spaces['hist'][indent] += 1 endfor " If 'expandtab' is set, but more lines in the existing buffer have tab " indents than space indents, we need to unset it and switch to pure tab " indenting; that's straightforward, we just need to make sure 'shiftwidth' " matches 'tabstop', and that 'softtabstop' is off. " if &expandtab && tabs > spaces['total'] setlocal noexpandtab softtabstop=0 let &l:shiftwidth = &tabstop " If 'expandtab' is unset, but more lines in the existing buffer have space " indents than tab indents, we need to set it, and guess an appropriate " 'shiftwidth'. " elseif !&expandtab && tabs < spaces['total'] setlocal expandtab " Iterate through the keys of the histogram from smallest to largest. " We'll accept as our 'shiftwidth' the smallest indent level that " constitutes more than 5% of the total lines, configurable by the user. " This is just an heuristic, but it seems to work pretty well. " let shiftwidth = 0 let indents = keys(spaces['hist']) call map(indents, 'str2nr(v:val)') " Coerce the string keys to numeric call sort(indents, 's:CompareNumeric') " Force numeric sort for shiftwidth in indents if spaces['hist'][shiftwidth] * 100 / spaces['total'] \ >= get(g:, 'detect_indent_confidence', 5) break endif endfor " We have our 'shiftwidth'; set it, with 'softtabstop' set to match let &l:shiftwidth = shiftwidth let &l:softtabstop = shiftwidth " If the 'expandtab' setting looks like it was already accurate to the " buffer, stop here else return endif " Since we just messed with indent settings, stack up a command to revert " the changes when the indent plugin is unloaded, as if we ourselves were " a single filetype indent plugin. " let undo_indent = 'setlocal expandtab< shiftwidth< softtabstop<' if exists('b:undo_indent') let b:undo_indent .= '|' . undo_indent else let b:undo_indent = undo_indent endif endfunction