summaryrefslogtreecommitdiff
path: root/opt/sleuth
diff options
context:
space:
mode:
Diffstat (limited to 'opt/sleuth')
-rw-r--r--opt/sleuth/README.markdown49
-rw-r--r--opt/sleuth/doc/sleuth.txt34
-rw-r--r--opt/sleuth/plugin/sleuth.vim206
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: