diff options
Diffstat (limited to 'opt/sleuth')
-rw-r--r-- | opt/sleuth/README.markdown | 49 | ||||
-rw-r--r-- | opt/sleuth/doc/sleuth.txt | 34 | ||||
-rw-r--r-- | opt/sleuth/plugin/sleuth.vim | 206 |
3 files changed, 289 insertions, 0 deletions
diff --git a/opt/sleuth/README.markdown b/opt/sleuth/README.markdown new file mode 100644 index 0000000..de4aadf --- /dev/null +++ b/opt/sleuth/README.markdown @@ -0,0 +1,49 @@ +# sleuth.vim + +This plugin automatically adjusts `'shiftwidth'` and `'expandtab'` +heuristically based on the current file, or, in the case the current file is +new, blank, or otherwise insufficient, by looking at other files of the same +type in the current and parent directories. In lieu of adjusting +`'softtabstop'`, `'smarttab'` is enabled. + +Compare to [DetectIndent][]. I wrote this because I wanted something fully +automatic. My goal is that by installing this plugin, you can remove all +indenting related configuration from your vimrc. + +[DetectIndent]: http://www.vim.org/scripts/script.php?script_id=1171 + +## Installation + +Install using your favorite package manager, or use Vim's built-in package +support: + + mkdir -p ~/.vim/pack/tpope/start + cd ~/.vim/pack/tpope/start + git clone https://tpope.io/vim/sleuth.git + vim -u NONE -c "helptags sleuth/doc" -c q + +## Notes + +* Searching for other files of the same type continues up the directory + hierarchy until a match is found. This means, for example, the indent for + the first file in a brand new Ruby project might very well be derived from + your `.irbrc`. I consider this a feature. +* If your file is consistently indented with hard tabs, `'shiftwidth'` will be + set to your `'tabstop'`. Otherwise, a `'tabstop'` of 8 is enforced. +* The algorithm is rolled from scratch, fairly simplistic, and only lightly + battle tested. It's probably not (yet) as good as [DetectIndent][]. + Let me know what it fails on for you. + +## Self-Promotion + +Like sleuth.vim? Follow the repository on +[GitHub](https://github.com/tpope/vim-sleuth) and vote for it on +[vim.org](http://www.vim.org/scripts/script.php?script_id=4375). And if +you're feeling especially charitable, follow [tpope](http://tpo.pe/) on +[Twitter](http://twitter.com/tpope) and +[GitHub](https://github.com/tpope). + +## License + +Copyright © Tim Pope. Distributed under the same terms as Vim itself. +See `:help license`. diff --git a/opt/sleuth/doc/sleuth.txt b/opt/sleuth/doc/sleuth.txt new file mode 100644 index 0000000..76832a0 --- /dev/null +++ b/opt/sleuth/doc/sleuth.txt @@ -0,0 +1,34 @@ +*sleuth.txt* Heuristically set buffer options + +Author: Tim Pope <http://tpo.pe/> +Repo: https://github.com/tpope/vim-sleuth +License: Same terms as Vim itself (see |license|) + +This plugin is only available if 'compatible' is not set. + +SUMMARY *sleuth* + +This plugin automatically adjusts 'shiftwidth' and 'expandtab' heuristically +based on the current file, or, in the case the current file is new, blank, or +otherwise insufficient, by looking at other files of the same type in the +current and parent directories. In lieu of adjusting 'softtabstop', +'smarttab' is enabled. + + *:Sleuth* +:Sleuth Manually detect indentation. + + *SleuthIndicator()* +SleuthIndicator() Indicator for inclusion in 'statusline'. Or use + flagship.vim to have it included automatically. + +SETTINGS *sleuth-settings* + +Automatic detection of buffer options can be controlled with: +> + let g:sleuth_automatic = 0 +< +or: +> + let b:sleuth_automatic = 0 +< + vim:tw=78:et:ft=help:norl: diff --git a/opt/sleuth/plugin/sleuth.vim b/opt/sleuth/plugin/sleuth.vim new file mode 100644 index 0000000..e5f6355 --- /dev/null +++ b/opt/sleuth/plugin/sleuth.vim @@ -0,0 +1,206 @@ +" 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 xmlcomment = 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 =~# '^\s*<\!--' + let xmlcomment = 1 + endif + if xmlcomment + if line =~# '-->' + let xmlcomment = 0 + endif + continue + 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 && (indent < 4 || indent % 2 == 0) && + \ 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'], + \ 'vim': ['vimrc', '.vimrc', '_vimrc'], + \ } + 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 &buftype ==# 'help' + return + endif + + let options = s:guess(getline(1, 1024)) + if s:apply_if_ready(options) + return + endif + let c = get(b:, 'sleuth_neighbor_limit', get(g:, 'sleuth_neighbor_limit', 20)) + let patterns = c > 0 ? s:patterns_for(&filetype) : [] + call filter(patterns, 'v:val !~# "/"') + let dir = expand('%:p:h') + while isdirectory(dir) && dir !=# fnamemodify(dir, ':h') && c > 0 + 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') + let c -= 1 + endif + if s:apply_if_ready(options) + let b:sleuth_culprit = neighbor + return + endif + if c <= 0 + break + endif + endfor + if c <= 0 + break + endif + 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 + let sw = &shiftwidth ? &shiftwidth : &tabstop + if &expandtab + return 'sw='.sw + elseif &tabstop == sw + return 'ts='.&tabstop + else + return 'sw='.sw.',ts='.&tabstop + endif +endfunction + +augroup sleuth + autocmd! + autocmd FileType * + \ if get(b:, 'sleuth_automatic', get(g:, 'sleuth_automatic', 1)) + \ | call s:detect() | endif + autocmd User Flags call Hoist('buffer', 5, 'SleuthIndicator') +augroup END + +command! -bar -bang Sleuth call s:detect() + +" vim:set et sw=2: |