├── .travis.yml ├── README.md ├── appveyor.yml ├── autoload └── findent.vim ├── doc └── vim-findent.txt ├── plugin └── findent.vim ├── scripts ├── install_vim.sh ├── run_doc.sh ├── run_themis.sh └── run_vimlint.sh └── test ├── .themisrc ├── dat ├── 2spaces.js ├── 4spaces.js ├── 8spaces.js ├── bsd_knf.js ├── tab.js ├── ts6sw2.js └── ts8sw4.js └── findent.vimspec /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | env: 3 | - HEAD=yes 4 | - HEAD=no 5 | sudo: false 6 | 7 | addons: 8 | apt: 9 | packages: 10 | - language-pack-ja 11 | - vim 12 | - libperl-dev 13 | - python-dev 14 | - python3-dev 15 | - liblua5.1-0-dev 16 | - lua5.1 17 | 18 | install: 19 | - bash scripts/install_vim.sh 20 | - if [ "$HEAD" = "yes" ]; then export PATH=$HOME/vim/bin:$PATH; fi 21 | 22 | before_script: 23 | - export VIMLINT=/tmp/vim-vimlint 24 | - export VIMLPARSER=/tmp/vim-vimlparser 25 | - export VIMTHEMIS=/tmp/vim-themis 26 | - export VIMPROC=/tmp/vimproc.vim 27 | - git clone https://github.com/syngan/vim-vimlint --single-branch --depth 1 $VIMLINT 28 | - git clone https://github.com/ynkdir/vim-vimlparser --single-branch --depth 1 $VIMLPARSER 29 | - git clone https://github.com/thinca/vim-themis --single-branch --depth 1 $VIMTHEMIS 30 | - git clone https://github.com/Shougo/vimproc.vim --single-branch --depth 1 $VIMPROC 31 | - (cd $VIMPROC && make) 32 | 33 | script: 34 | - which -a vim 35 | - vim --cmd version --cmd quit 36 | - ./scripts/run_doc.sh 37 | - ./scripts/run_vimlint.sh 38 | - ./scripts/run_themis.sh 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vim-findent [![Build status](https://travis-ci.org/lambdalisue/vim-findent.svg?branch=master)](https://travis-ci.org/lambdalisue/vim-findent) [![Build status](https://ci.appveyor.com/api/projects/status/p7orkdddc08v4lvk/branch/master?svg=true)](https://ci.appveyor.com/project/lambdalisue/vim-findent/branch/master) 2 | =============================================================================== 3 | 4 | 5 | *vim-findent* is a plugin to find and apply reasonable values of `expandtab`, `shiftwidth`, `tabstop`, and `softtabstop` (an indent rule) from the content of the current buffer. 6 | 7 | vim-findent is a small and simple Vim plugin, compares to existing similar plugins such as [vim-sleuth](https://github.com/tpope/vim-sleuth) or [detectindent](https://github.com/ciaranm/detectindent). 8 | This plugin only provides two commands and no default `FileType` `autocmd` is provided, mean that users can control the behavior of automatic detection by defining `autocmd` by themselves. 9 | The detection algorithm is much simpler as well compared to vim-sleuth or detectindent. 10 | While we are living in a real world and we are editing pre-formatted codes, I don't really think we need a super perfect but complex algorithm. 11 | A faster and simpler algorithm would be better. 12 | 13 | See [Test cases](./test/dat) to figure out what kind of indent rules can be detected via this plugin. 14 | 15 | Install 16 | ------------------------------------------------------------------------------- 17 | 18 | Use Vundle.vim, neobundle.vim, or other vim plugin manager to install it like: 19 | 20 | ```vim 21 | " Vundle.vim 22 | Plugin 'lambdalisue/vim-findent' 23 | 24 | " neobundle.vim 25 | NeoBundle 'lambdalisue/vim-findent' 26 | 27 | " neobundle.vim (Lazy) 28 | NeoBundleLazy 'lambdalisue/vim-findent', { 29 | \ 'autoload': { 30 | \ 'commands': [ 31 | \ 'Findent', 32 | \ 'FindentRestore', 33 | \ ], 34 | \}} 35 | 36 | " neobundle.vim (Lazy: with completion) 37 | NeoBundleLazy 'lambdalisue/vim-findent', { 38 | \ 'autoload': { 39 | \ 'commands': [ 40 | \ { 41 | \ 'name': 'Findent', 42 | \ 'complete': 'customlist,findent#FindentComplete', 43 | \ }, 44 | \ { 45 | \ 'name': 'FindentRestore', 46 | \ 'complete': 'customlist,findent#FindentRestoreComplete', 47 | \ }, 48 | \ ], 49 | \}} 50 | ``` 51 | 52 | If you are not using any vim plugin manager, you can copy the repository to 53 | your `$VIM` directory to enable the plugin. 54 | 55 | 56 | Usage 57 | ------------------------------------------------------------------------------- 58 | 59 | Call `:Findent` to find and apply reasonable `expandtab`, `shiftwidth`, `tabstop`, and `softtabstop` (an indent rule) of 60 | the current buffer. 61 | It automatically use a middle part of the content to detect the reasonable indent rule and apply to the local setting (`setlocal`). 62 | If you want to control the region of content used for detection, use visual selection to select the region prior to the command. 63 | 64 | Call `:FindentRestore` to restore a previous indent rule of the current buffer or call `:Findent!` to re-apply forcedly. 65 | 66 | If you want to make this detection automatic, use `autocmd` like: 67 | 68 | ```vim 69 | augroup findent 70 | autocmd! 71 | autocmd FileType javascript Findent 72 | autocmd FileType css Findent 73 | augroup END 74 | ``` 75 | 76 | If you feel annoying for the detection message, use '--no-messages': 77 | 78 | ```vim 79 | augroup findent 80 | autocmd! 81 | autocmd FileType javascript Findent --no-messages 82 | autocmd FileType css Findent --no-messages 83 | augroup END 84 | ``` 85 | 86 | Or if you want to completely suppress messages, use '--no-warnings' as well: 87 | 88 | ```vim 89 | augroup findent 90 | autocmd! 91 | autocmd FileType javascript Findent --no-messages --no-warnings 92 | autocmd FileType css Findent --no-messages --no-warnings 93 | augroup END 94 | ``` 95 | 96 | In this case, `Findent` reserve the specified options into a buffer variable and 97 | actual `Findent` call will be performed on `BufWinEnter` autocmd. 98 | This is required to make sure that the `Findent` command is called only after `FileType` autocmd or `ftplugin`. 99 | See `:help Findent-autocmd` for more detail about this behavior. 100 | 101 | Command and Variable 102 | ------------------------------------------------------------------------------- 103 | 104 | See `:help vim-findent-command` or `:help vim-findent-variable`. 105 | 106 | 107 | License 108 | -------------------------------------------------------------------------------- 109 | Copyright (c) 2015 Alisue, hashnote.net 110 | 111 | Permission is hereby granted, free of charge, to any person obtaining 112 | a copy of this software and associated documentation files 113 | (the "Software"), to deal in the Software without restriction, 114 | including without limitation the rights to use, copy, modify, merge, 115 | publish, distribute, sublicense, and/or sell copies of the Software, 116 | and to permit persons to whom the Software is furnished to do so, 117 | subject to the following conditions: 118 | 119 | The above copyright notice and this permission notice shall be 120 | included in all copies or substantial portions of the Software. 121 | 122 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 123 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 124 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 125 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 126 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 127 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 128 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 129 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}-{branch}' 2 | 3 | # branches to build 4 | branches: 5 | except: 6 | - gh-pages 7 | skip_tags: true 8 | clone_depth: 1 9 | 10 | # environment variables (%TEMP% cannot be used in this section) 11 | environment: 12 | matrix: 13 | - VIM_URL: http://files.kaoriya.net/vim/vim74-kaoriya-win64.zip 14 | # Vim 7.3 in Windows show somehow 'E65 Illegal back reference' so don't support for now 15 | # https://ci.appveyor.com/project/lambdalisue/vim-gita/build/job/uxym4f2ngao20s84 16 | # - VIM_URL: http://files.kaoriya.net/vim/2011/vim73-kaoriya-win64-20110306.zip 17 | 18 | # scripts that run after cloning repository 19 | install: 20 | - cd %TEMP% 21 | 22 | # Install vim 23 | - appveyor DownloadFile %VIM_URL% -FileName vim.zip 24 | - 7z x vim.zip > nul 25 | - ps: $env:THEMIS_VIM = (Get-ChildItem . | Select-Object -First 1).FullName + '\vim.exe' 26 | 27 | # Install vim-themis 28 | - git -c advice.detachedHead=false clone --single-branch --depth 1 --branch v1.5 ^ 29 | https://github.com/thinca/vim-themis 30 | # Install vimproc.vim 31 | - git -c advice.detachedHead=false clone --single-branch --depth 1 --branch ver.9.2 ^ 32 | https://github.com/Shougo/vimproc.vim 33 | - appveyor DownloadFile https://github.com/Shougo/vimproc.vim/releases/download/ver.9.2/vimproc_win64.dll ^ 34 | -FileName vimproc.vim\lib\vimproc_win64.dll 35 | 36 | - cd %APPVEYOR_BUILD_FOLDER% 37 | 38 | # disable building 39 | build: off 40 | 41 | # test 42 | test_script: 43 | - call %THEMIS_VIM% --version 44 | - call %TEMP%\vim-themis\bin\themis.bat --reporter dot ^ 45 | --runtimepath %TEMP%/vimproc.vim 46 | 47 | # disable deployment 48 | deploy: off 49 | -------------------------------------------------------------------------------- /autoload/findent.vim: -------------------------------------------------------------------------------- 1 | let s:save_cpo = &cpoptions 2 | set cpoptions&vim 3 | 4 | function! s:parse_args(args, ...) abort " {{{ 5 | let options = get(a:000, 0, {}) 6 | for arg in a:args 7 | if arg ==# '-h' || arg ==# '--help' 8 | let options.help = 1 9 | elseif arg ==# '--messages' 10 | let options.messages = 1 11 | elseif arg ==# '--no-messages' 12 | let options.messages = 0 13 | elseif arg ==# '--warnings' 14 | let options.warnings = 1 15 | elseif arg ==# '--no-warnings' 16 | let options.warnings = 0 17 | elseif arg =~# '^--threshold=\d\+' 18 | let options.threshold = str2nr(matchstr(arg, '^--threshold=\zs\d\+')) 19 | elseif arg =~# '^--chunksize=\d\+' 20 | let options.chunksize = str2nr(matchstr(arg, '^--chunksize=\zs\d\+')) 21 | else 22 | echohl ErrorMsg 23 | echo printf('Unknown option "%s" is specified', arg) 24 | echohl None 25 | return { 'failed': 1 } 26 | endif 27 | endfor 28 | return options 29 | endfunction " }}} 30 | function! s:filter_minority(candidates, ...) abort " {{{ 31 | let alpha = get(a:000, 0, 0.05) 32 | let threshold = len(a:candidates) * alpha 33 | let frequency = {} 34 | for candidate in a:candidates 35 | let frequency[candidate] = get(frequency, candidate, 0) + 1 36 | endfor 37 | return filter(copy(a:candidates), 'frequency[v:val] > threshold') 38 | endfunction " }}} 39 | function! s:guess_tab_style(spaces, tabs, leadings, threshold) abort " {{{ 40 | let tab_spaces = s:filter_minority(filter( 41 | \ map(copy(a:leadings), 'len(matchstr(v:val, "^\\t\\+\\zs \\+"))'), 42 | \ 'index(g:findent#samples, v:val) >= 0' 43 | \)) 44 | if !empty(tab_spaces) 45 | " Vim(C)/Ruby(C) or BSD/KNF style variant (shiftwidth=4, tabstop=8) 46 | " Use 'spaces' instead of 'tab_spaces' for determine the maximum number 47 | " of spaces to improve detection accuracy 48 | return { 49 | \ 'expandtab': 0, 50 | \ 'shiftwidth': min(a:spaces), 51 | \ 'tabstop': min(a:spaces) + max(a:spaces), 52 | \ 'softtabstop': 0, 53 | \} 54 | else 55 | " Tab indent style 56 | return { 57 | \ 'expandtab': 0, 58 | \ 'shiftwidth': 0, 59 | \ 'softtabstop': 0, 60 | \} 61 | endif 62 | endfunction " }}} 63 | function! s:guess_space_style(spaces, tabs, leadings, threshold) abort " {{{ 64 | let topscore = 0 65 | let unit = 0 66 | for sample in g:findent#samples 67 | let score = len(filter(copy(a:spaces), 'v:val == sample')) 68 | if score > topscore 69 | let unit = sample 70 | let topscore = score 71 | if a:threshold > 0 && score >= a:threshold 72 | break 73 | endif 74 | endif 75 | endfor 76 | if topscore == 0 77 | return {} 78 | endif 79 | return { 80 | \ 'expandtab': 1, 81 | \ 'shiftwidth': unit, 82 | \ 'softtabstop': unit, 83 | \} 84 | endfunction " }}} 85 | 86 | function! findent#guess(content, ...) abort " {{{ 87 | let threshold = get(a:000, 0, g:findent#threshold) 88 | let leadings = filter( 89 | \ map(a:content, 'matchstr(v:val, "^\\%(\\t\\| \\)\\+")'), 90 | \ '!empty(v:val)' 91 | \) 92 | if empty(leadings) 93 | " failed to guess 94 | return {} 95 | endif 96 | let spaces = s:filter_minority(filter( 97 | \ map(copy(leadings), 'len(matchstr(v:val, "^ \\+"))'), 98 | \ 'index(g:findent#samples, v:val) >= 0' 99 | \)) 100 | let tabs = s:filter_minority(filter( 101 | \ map(copy(leadings), 'len(matchstr(v:val, "^\\t\\+"))'), 102 | \ 'v:val' 103 | \)) 104 | if len(spaces) <= len(tabs) 105 | return s:guess_tab_style(spaces, tabs, leadings, threshold) 106 | else 107 | return s:guess_space_style(spaces, tabs, leadings, threshold) 108 | endif 109 | endfunction " }}} 110 | function! findent#apply(...) abort " {{{ 111 | let options = extend({ 112 | \ 'force': 0, 113 | \ 'messages': g:findent#enable_messages, 114 | \ 'warnings': g:findent#enable_warnings, 115 | \ 'threshold': g:findent#threshold, 116 | \ 'chunksize': g:findent#chunksize, 117 | \ 'startline': 0, 118 | \ 'lastline': 0, 119 | \}, get(a:000, 0, {}) 120 | \) 121 | if exists('b:_findent') 122 | if options.force 123 | call findent#restore({'messages': 0, 'warnings': 0}) 124 | else 125 | if options.warnings 126 | echohl WarningMsg 127 | echo printf( 128 | \ 'findent has already applied (%s, shiftwidth=%d, tabstop=%d, and softtabstop=%d)', 129 | \ &l:expandtab ? 'expandtab' : 'noexpandtab', 130 | \ &l:shiftwidth, 131 | \ &l:tabstop, 132 | \ &l:softtabstop, 133 | \) 134 | echohl None 135 | endif 136 | return 137 | endif 138 | endif 139 | let N = line('$') 140 | if options.startline == options.lastline 141 | " calculate reasonable startline and lastline 142 | let startline = N - float2nr(round(options.chunksize / 2)) 143 | let lastline = startline + options.chunksize 144 | else 145 | " use specified startline and lastline 146 | let startline = options.startline 147 | let lastline = options.lastline 148 | endif 149 | let startline = max([0, startline]) 150 | let lastline = min([N, lastline]) 151 | let content = getline(startline, lastline) 152 | let meta = call('findent#guess', [content, options.threshold]) 153 | if empty(meta) 154 | if options.warnings 155 | echohl WarningMsg 156 | echo 'findent has failed to guess the indent rule of the current buffer' 157 | echohl None 158 | endif 159 | return 160 | endif 161 | 162 | " check the guessed tabstop value because 'tabstop' must be bigger than 0 163 | let tabstop = get(meta, 'tabstop', 0) 164 | if tabstop <= 0 165 | let tabstop = &l:tabstop 166 | endif 167 | 168 | let meta.previous = {} 169 | let meta.previous.expandtab = &l:expandtab 170 | let meta.previous.shiftwidth = &l:shiftwidth 171 | let meta.previous.tabstop = &l:tabstop 172 | let meta.previous.softtabstop = &l:softtabstop 173 | let &l:expandtab = meta.expandtab 174 | let &l:shiftwidth = meta.shiftwidth 175 | let &l:tabstop = tabstop 176 | let &l:softtabstop = get(meta, 'softtabstop', &l:softtabstop) 177 | let b:_findent = meta 178 | if options.messages 179 | echo printf( 180 | \ 'findent is applied (%s, shiftwidth=%d, tabstop=%d, and softtabstop=%d)', 181 | \ &l:expandtab ? 'expandtab' : 'noexpandtab', 182 | \ &l:shiftwidth, 183 | \ &l:tabstop, 184 | \ &l:softtabstop, 185 | \) 186 | endif 187 | endfunction " }}} 188 | function! findent#restore(...) abort " {{{ 189 | let options = extend({ 190 | \ 'messages': g:findent#enable_messages, 191 | \ 'warnings': g:findent#enable_warnings, 192 | \}, get(a:000, 0, {}) 193 | \) 194 | if !exists('b:_findent') 195 | if options.warnings 196 | echohl WarningMsg 197 | echo printf( 198 | \ 'findent has not applied yet (%s, shiftwidth=%d, tabstop=%d, and softtabstop=%d)', 199 | \ &l:expandtab ? 'expandtab' : 'noexpandtab', 200 | \ &l:shiftwidth, 201 | \ &l:tabstop, 202 | \ &l:softtabstop, 203 | \) 204 | echohl None 205 | endif 206 | return 207 | endif 208 | let meta = b:_findent 209 | let &l:expandtab = meta.previous.expandtab 210 | let &l:shiftwidth = meta.previous.shiftwidth 211 | let &l:tabstop = meta.previous.tabstop 212 | let &l:softtabstop = meta.previous.softtabstop 213 | unlet! b:_findent 214 | if options.messages 215 | echo printf( 216 | \ 'Findent is restored (%s, shiftwidth=%d, tabstop=%d, and softtabstop=%d)', 217 | \ &l:expandtab ? 'expandtab' : 'noexpandtab', 218 | \ &l:shiftwidth, 219 | \ &l:tabstop, 220 | \ &l:softtabstop, 221 | \) 222 | endif 223 | endfunction " }}} 224 | 225 | function! findent#Findent(bang, line1, line2, args) abort " {{{ 226 | let options = s:parse_args(a:args, { 227 | \ 'force': a:bang ==# '!', 228 | \ 'startline': a:line1, 229 | \ 'lastline': a:line2, 230 | \}) 231 | if get(options, 'failed') 232 | " failed to parse 233 | return 234 | endif 235 | 236 | if get(options, 'help') 237 | redraw 238 | echo ':Findent[!] [-h|--help] [--[no-]messages] [--[no-]warnings] [--chunksize={CHUNKSIZE}] [--threshold={THRESHOLD}]' 239 | echo ' ' 240 | echo 'Find and apply a reasonable indent rule of the current buffer' 241 | echo ' ' 242 | echo 'Optional arguments:' 243 | echo ' -h, --help Show this help' 244 | echo ' --[no-]messages Show detection messages' 245 | echo ' --[no-]warnings Show warning messages' 246 | echo ' --chunksize={CHUNKSIZE} Specify chunksize (the number of lines) of the content' 247 | echo ' --threshold={THRESHOLD} Specify detection threshold of a space indent' 248 | else 249 | if empty(expand('')) || options.force 250 | " Findent has called manually or with bang 251 | redraw 252 | call findent#apply(options) 253 | else 254 | " Findent called in autocmd so reserve options to call Findent 255 | " in BufWinEnter to make sure that Findent is called after filetype 256 | " specification 257 | let b:_findent_reserved = extend(options, { 'force': 1 }) 258 | endif 259 | endif 260 | endfunction " }}} 261 | function! findent#FindentRestore(args) abort " {{{ 262 | let options = s:parse_args(a:args) 263 | if get(options, 'failed') 264 | " failed to parse 265 | return 266 | endif 267 | 268 | redraw 269 | if get(options, 'help') 270 | echo ':FindentRestore [-h|--help] [--[no-]messages] [--[no-]warnings]' 271 | echo ' ' 272 | echo 'Restore a previous indent rule of the current buffer' 273 | echo ' ' 274 | echo 'Optional arguments:' 275 | echo ' -h, --help Show this help' 276 | echo ' --[no-]messages Show detection messages' 277 | echo ' --[no-]warnings Show warning messages' 278 | else 279 | call findent#restore(options) 280 | endif 281 | endfunction " }}} 282 | function! findent#FindentComplete(arglead, cmdline, cursorpos) abort " {{{ 283 | let candidates = [ 284 | \ '-h', '--help', 285 | \ '--messages', '--no-messages', 286 | \ '--warnings', '--no-warnings', 287 | \ '--threshold=', 288 | \ '--chunksize=', 289 | \] 290 | return filter(candidates, printf('v:val =~# "^%s"', a:arglead)) 291 | endfunction " }}} 292 | function! findent#FindentRestoreComplete(arglead, cmdline, cursorpos) abort " {{{ 293 | let candidates = [ 294 | \ '-h', '--help', 295 | \ '--messages', '--no-messages', 296 | \ '--warnings', '--no-warnings', 297 | \] 298 | return filter(candidates, printf('v:val =~# "^%s"', a:arglead)) 299 | endfunction " }}} 300 | 301 | let s:default = { 302 | \ 'enable_messages': 1, 303 | \ 'enable_warnings': 1, 304 | \ 'chunksize': 1000, 305 | \ 'threshold': 1, 306 | \ 'samples': [2, 4, 8], 307 | \} 308 | function! s:init() abort " {{{ 309 | for [key, value] in items(s:default) 310 | let key = 'g:findent#' . key 311 | if !exists(key) 312 | silent execute printf('let %s = %s', key, string(value)) 313 | endif 314 | unlet value 315 | endfor 316 | endfunction " }}} 317 | call s:init() 318 | 319 | let &cpoptions = s:save_cpo 320 | " vim:set et ts=2 sts=2 sw=2 tw=0 fdm=marker: 321 | -------------------------------------------------------------------------------- /doc/vim-findent.txt: -------------------------------------------------------------------------------- 1 | *vim-findent.txt* Find and apply the existing indent rule 2 | 3 | Version: 0.1.0 4 | Author: Alisue *vim-findent-author* 5 | Support: Vim 7.4 and above 6 | License: MIT license 7 | 8 | Copyright (c) 2015 Alisue, hashnote.net 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining 11 | a copy of this software and associated documentation files 12 | (the "Software"), to deal in the Software without restriction, 13 | including without limitation the rights to use, copy, modify, merge, 14 | publish, distribute, sublicense, and/or sell copies of the Software, 15 | and to permit persons to whom the Software is furnished to do so, 16 | subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | ============================================================================= 30 | CONTENTS *vim-findent-contents* 31 | 32 | Introduction |vim-findent-introduction| 33 | Install |vim-findent-install| 34 | Usage |vim-findent-usage| 35 | Interface |vim-findent-interface| 36 | Command |vim-findent-command| 37 | Variable |vim-findent-variable| 38 | 39 | 40 | ============================================================================== 41 | INTRODUCTION *vim-findent-introduction* 42 | 43 | *vim-findent* is a plugin to find and apply reasonable values of |expandtab|, 44 | |shiftwidth|, |tabstop|, and |softtabstop| of the current buffer. 45 | 46 | vim-findent is small and simple plugin, compares to existing similar plugins 47 | such as vim-sleuth or detectindent. This plugin only provides two commands and 48 | no default |FileType| |autocmd| is provided, mean that users can control the 49 | behavior of automatic detection by defining |autocmd| by themselves. 50 | The detection algorithm is much simpler as well compared to vim-sleuth or 51 | detectindent. While we are living in a real world and we are editing 52 | pre-formatted codes, I don't really think we need a super perfect but complex 53 | algorithm. A faster and simpler algorithm would be better. 54 | 55 | 56 | ============================================================================== 57 | INSTALL *vim-findent-install* 58 | 59 | Use Vundle.vim, neobundle.vim, or other vim plugin manager to install it like: 60 | > 61 | " Vundle.vim 62 | Plugin 'lambdalisue/vim-findent' 63 | 64 | " neobundle.vim 65 | NeoBundle 'lambdalisue/vim-findent' 66 | 67 | " neobundle.vim (Lazy) 68 | NeoBundleLazy 'lambdalisue/vim-findent', { 69 | \ 'autoload': { 70 | \ 'commands': [ 71 | \ 'Findent', 72 | \ 'FindentRestore', 73 | \ ], 74 | \}} 75 | 76 | " neobundle.vim (Lazy: with completion) 77 | NeoBundleLazy 'lambdalisue/vim-findent', { 78 | \ 'autoload': { 79 | \ 'commands': [ 80 | \ { 81 | \ 'name': 'Findent', 82 | \ 'complete': 'customlist,findent#FindentComplete', 83 | \ }, 84 | \ { 85 | \ 'name': 'FindentRestore', 86 | \ 'complete': 'customlist,findent#FindentRestoreComplete', 87 | \ }, 88 | \ ], 89 | \}} 90 | < 91 | If you are not using any vim plugin manager, you can copy the repository to 92 | your $VIM directory to enable the plugin. 93 | 94 | 95 | ============================================================================== 96 | USAGE *vim-findent-usage* 97 | 98 | Call |:Findent| to find and apply reasonable |expandtab|, |shiftwidth|, 99 | |tabstop|, and |softtabstop| (an indent rule) of the current buffer. 100 | It automatically use a middle part of the content to detect the reasonable 101 | indent rule and apply to the local setting (|setlocal|). 102 | If you want to control the region of content used for detection, use visual 103 | selection to select the region prior to the command. 104 | 105 | Call |:FindentRestore| to restore a previous indent rule of the current buffer, 106 | or call |:Findent!| to re-apply findent forcedly. 107 | 108 | If you want to make this detection automatic, use |autocmd| like: 109 | > 110 | augroup findent 111 | autocmd! 112 | autocmd FileType javascript Findent 113 | autocmd FileType css Findent 114 | augroup END 115 | < 116 | If you feel annoying for the detection message, use '--no-messages': 117 | > 118 | augroup findent 119 | autocmd! 120 | autocmd FileType javascript Findent --no-messages 121 | autocmd FileType css Findent --no-messages 122 | augroup END 123 | < 124 | Or if you want to completely suppress messages, use '--no-warnings' as well: 125 | > 126 | augroup findent 127 | autocmd! 128 | autocmd FileType javascript Findent --no-messages --no-warnings 129 | autocmd FileType css Findent --no-messages --no-warnings 130 | augroup END 131 | < 132 | In this case, |:Findent| reserve the specified options into a buffer variable 133 | and actual |:Findent| call will be performed on |BufWinEnter| autocmd. 134 | This is required to make sure that the |:Findent| command is called only after 135 | |FileType| autocmd or |ftplugin|. 136 | See |:Findent-autocmd| for more detail about this behavior. 137 | 138 | ============================================================================== 139 | INTERFACE *vim-findent-interface* 140 | 141 | ------------------------------------------------------------------------------- 142 | COMMAND *vim-findent-command* 143 | 144 | *:Findent* 145 | :Findent[!] [-h|--help] [--[no-]messages] [--[no-]warnings] 146 | [--chunksize={CHUNKSIZE}] [--threshold={THRESHOLD}] 147 | 148 | Find and apply reasonable values of |expandtab|, |shiftwidth|, |tabstop|, 149 | and |softtabstop| of the current buffer. 150 | If a || (!) is specified, it force to find and apply the 151 | reasonable indent rule. Otherwise it will fail if the indent rules 152 | are already applied. 153 | 154 | If '--no-messages' is specified, detection messages will be 155 | suppressed. To enforce to display the messages, use '--messages'. 156 | The default behavior is depended on |g:findent#enable_messages|. 157 | If '--no-warnings' is specified, warning messages will be 158 | suppressed. To enforce to display the messages, use '--warnings'. 159 | The default behavior is depended on |g:findent#enable_warnings|. 160 | 161 | The detection will be performed on {CHUNKSIZE} lines at the middle 162 | part of the content of the current buffer. To control the region of 163 | the detection, use visual selection prior to the command. 164 | The default value of {CHUNKSIZE} is |g:findent#chunksize|. 165 | 166 | The detected number of spaces in a space indent will be influenced by 167 | {THRESHOLD}. If {THRESHOLD} is 0, the number of appearance of each 168 | sample in |g:findent#samples| are counted and most frequent sample 169 | would be the number of spaces of a unit. However, if {THRESHOLD} is 170 | grater than 0, the detection procedure will be complete when the number 171 | of appearance of a sample reaches to the {THRESHOLD}. This {THRESHOLD} 172 | stands for detecting a correct indent rule from a content like below: 173 | > 174 | " expandtab, shiftwidth=2, softtabstop=2 175 | function! FizzBuzz(n) abort 176 | function! s:fizzbuzz(i) abort 177 | let mod3 = i % 3 == 0 ? 'Fizz' : '' 178 | let mod5 = i % 5 == 0 ? 'Buzz' : '' 179 | let ret = mod3 . mod5 180 | return empty(ret) ? string(i) : ret 181 | endfunction 182 | endfunction 183 | < 184 | The content above has two lines with 2 leading spaces and four lines 185 | with 4 leading spaces. In this case, the detection program choose a 186 | dominant leading spaces (4 spaces) as the number of spaces of a unit 187 | if {THRESHOLD} is 0. 188 | The default value of {THRESHOLD} is |g:findent#threshold|. 189 | 190 | *:Findent-autocmd* 191 | If |:Findent| is called from |autocmd|, it automatically reserve the 192 | specified options to call |:Findent| in |BufWinEnter|. This is 193 | required to make sure that |:Findent| is called only after |FileType| 194 | |autocmd| and/or |ftplugin|. So that applied settings by |:Findent| 195 | will not be overwritten by filetype specific settings. 196 | If you want to call |:Findent| whenever the command is called even 197 | from |autocmd|, specify a || (!). 198 | 199 | To restore the indent rule, call |:FindentRestore|. 200 | 201 | *:FindentRestore* 202 | :FindentRestore [-h|--help] [--[no-]messages] [--[no-]warnings] 203 | 204 | Restore previous |expandtab|, |shiftwidth|, |tabstop|, and 205 | |softtabstop| of the current buffer saved by |:Findent|. 206 | 207 | If '--no-messages' is specified, detection messages will be 208 | suppressed. To enforce to display the messages, use '--messages'. 209 | The default behavior is depended on |g:findent#enable_messages|. 210 | If '--no-warnings' is specified, warning messages will be 211 | suppressed. To enforce to display the messages, use '--warnings'. 212 | The default behavior is depended on |g:findent#enable_warnings|. 213 | 214 | ------------------------------------------------------------------------------- 215 | VARIABLE *vim-findent-variable* 216 | 217 | g:findent#enable_messages *g:findent#enable_messages* 218 | 219 | Enable detection messages of |:Findent| and |:FindentRestore|. 220 | If the value is 0, users need to specify '--messages' to see the 221 | detection messages. 222 | Default is 1 223 | 224 | g:findent#enable_warnings *g:findent#enable_warnings* 225 | 226 | Enable warning messages of |:Findent| and |:FindentRestore|. 227 | If the value is 0, users need to specify '--warnings' to see the 228 | warning messages. 229 | Default is 1 230 | 231 | g:findent#chunksize *g:findent#chunksize* 232 | 233 | A default value (|Number|) of {CHUNKSIZE} used in |:Findent|. 234 | Default is 1000 235 | 236 | g:findent#threshold *g:findent#threshold* 237 | 238 | A default value (|Number|) of {THRESHOLD} used in |:Findent|. 239 | Default is 1 240 | 241 | g:findent#samples *g:findent#samples* 242 | 243 | A |List| of the number of spaces of the unit (investigation sample). 244 | Only the number of spaces in this variable are allowed to be an indent 245 | unit to decrease mis-detections of |shiftwidth| and/or |softtabstop|. 246 | Posterior samples will not be investigated if |g:findent#threshold| is 247 | grater than 0 and the number of appearance of the prior sample reach 248 | the threshold. So pay attention to the order of sample in the list. 249 | Default is [2, 4, 8] 250 | 251 | vim:tw=78:fo=tcq2mM:ts=8:ft=help:norl 252 | -------------------------------------------------------------------------------- /plugin/findent.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_findent') 2 | finish 3 | endif 4 | let g:loaded_findent = 1 5 | 6 | let s:save_cpo = &cpoptions 7 | set cpoptions&vim 8 | 9 | command! -nargs=* -range -bang 10 | \ -complete=customlist,findent#FindentComplete 11 | \ Findent 12 | \ :call findent#Findent(, , , []) 13 | command! -nargs=* 14 | \ -complete=customlist,findent#FindentRestoreComplete 15 | \ FindentRestore 16 | \ :call findent#FindentRestore([]) 17 | 18 | augroup findent-process-reservation 19 | autocmd! 20 | autocmd BufWinEnter * 21 | \ if exists('b:_findent_reserved') | 22 | \ call findent#apply(b:_findent_reserved) | 23 | \ silent! unlet! b:_findent_reserved | 24 | \ endif 25 | augroup END 26 | 27 | 28 | let &cpoptions = s:save_cpo 29 | " vim:set et ts=2 sts=2 sw=2 tw=0 fdm=marker: 30 | -------------------------------------------------------------------------------- /scripts/install_vim.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | if [ "$HEAD" = "yes" ]; then 4 | git clone --depth 1 https://github.com/vim/vim /tmp/vim 5 | cd /tmp/vim 6 | ./configure \ 7 | --prefix="$HOME/vim" \ 8 | --enable-fail-if-missing \ 9 | --with-features=huge \ 10 | --enable-perlinterp \ 11 | --enable-pythoninterp \ 12 | --enable-python3interp \ 13 | --enable-rubyinterp \ 14 | --enable-luainterp 15 | make -j2 16 | make install 17 | fi 18 | -------------------------------------------------------------------------------- /scripts/run_doc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | vim --cmd "try | helptags doc/ | catch | cquit | endtry" --cmd quit 3 | -------------------------------------------------------------------------------- /scripts/run_themis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | : ${VIMTHEMIS:=~/.vim/bundle/vim-themis} 3 | : ${VIMPROC:=~/.vim/bundle/vimproc.vim} 4 | 5 | # themis 6 | sh ${VIMTHEMIS}/bin/themis \ 7 | --reporter dot \ 8 | --runtimepath ${VIMPROC} \ 9 | $@ 10 | 11 | -------------------------------------------------------------------------------- /scripts/run_vimlint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | : ${VIMLINT:=~/.vim/bundle/vim-vimlint} 3 | : ${VIMLPARSER:=~/.vim/bundle/vim-vimlparser} 4 | 5 | # vim-vimlint 6 | sh ${VIMLINT}/bin/vimlint.sh \ 7 | -l ${VIMLINT} -p ${VIMLPARSER} \ 8 | -e EVL103=1 -e EVL102.l:_=1 -c func_abort=1 \ 9 | autoload/findent 10 | -------------------------------------------------------------------------------- /test/.themisrc: -------------------------------------------------------------------------------- 1 | let g:file = expand(':p') 2 | let g:repo = fnamemodify(g:file, ':h') 3 | 4 | let s:assert = themis#helper('assert') 5 | call themis#option('recursive', 1) 6 | call themis#helper('command').with(s:assert) 7 | 8 | "vim: sts=2 sw=2 smarttab et ai textwidth=0 fdm=marker ft=vim 9 | -------------------------------------------------------------------------------- /test/dat/2spaces.js: -------------------------------------------------------------------------------- 1 | /* 2 | * FizzBuzz.js 3 | * 4 | * @author: Alisue 5 | * 6 | * Correct indent rule of this file 7 | * expandtab, shiftwidth=2, softtabstop=2 8 | * 9 | */ 10 | (function() { 11 | "use strict"; 12 | 13 | function FizzBuzz(n) { 14 | let fizzbuzz = function (n) { 15 | let mod3 = n % 3 == 0 ? 'Fizz' : ''; 16 | let mod5 = n % 5 == 0 ? 'Buzz' : ''; 17 | return (mod3 + mod5) || n; 18 | }; 19 | for (let i=0; i 5 | * 6 | * Correct indent rule of this file 7 | * expandtab, shiftwidth=4, softtabstop=4 8 | * 9 | */ 10 | (function() { 11 | "use strict"; 12 | 13 | function FizzBuzz(n) { 14 | let fizzbuzz = function (n) { 15 | let mod3 = n % 3 == 0 ? 'Fizz' : ''; 16 | let mod5 = n % 5 == 0 ? 'Buzz' : ''; 17 | return (mod3 + mod5) || n; 18 | }; 19 | for (let i=0; i 5 | * 6 | * Correct indent rule of this file 7 | * expandtab, shiftwidth=8, softtabstop=8 8 | * 9 | */ 10 | (function() { 11 | "use strict"; 12 | 13 | function FizzBuzz(n) { 14 | let fizzbuzz = function (n) { 15 | let mod3 = n % 3 == 0 ? 'Fizz' : ''; 16 | let mod5 = n % 5 == 0 ? 'Buzz' : ''; 17 | return (mod3 + mod5) || n; 18 | }; 19 | for (let i=0; i 5 | * 6 | * Correct indent rule of this file 7 | * noexpandtab, shiftwidth=4, tabstop=8, softtabstop=0 8 | * 9 | */ 10 | (function() { 11 | "use strict"; 12 | 13 | function FizzBuzz(n) { 14 | let fizzbuzz = function (n) { 15 | let mod3 = n % 3 == 0 16 | ? 'Fizz' 17 | : ''; 18 | let mod5 = n % 5 == 0 19 | ? 'Buzz' 20 | : ''; 21 | return (mod3 + mod5) || n; 22 | }; 23 | for (let i=0; i 5 | * 6 | * Correct indent rule of this file 7 | * noexpandtab, shiftwidth=0, softtabstop=0 8 | * 9 | */ 10 | (function() { 11 | "use strict"; 12 | 13 | function FizzBuzz(n) { 14 | let fizzbuzz = function (n) { 15 | let mod3 = n % 3 == 0 ? 'Fizz' : ''; 16 | let mod5 = n % 5 == 0 ? 'Buzz' : ''; 17 | return (mod3 + mod5) || n; 18 | }; 19 | for (let i=0; i 5 | * 6 | * Correct indent rule of this file 7 | * noexpandtab, shiftwidth=2, tabstop=6, softtabstop=0 8 | * 9 | */ 10 | (function() { 11 | "use strict"; 12 | 13 | function FizzBuzz(n) { 14 | let fizzbuzz = function (n) { 15 | let mod3 = n % 3 == 0; 16 | let mod5 = n % 5 == 0; 17 | 18 | if (mod3 && mod5) { 19 | return 'FizzBuzz'; 20 | } else if (mod3) { 21 | return 'Fizz'; 22 | } else if (mod5) { 23 | return 'Buzz'; 24 | } else { 25 | return n; 26 | } 27 | }; 28 | for (let i=0; i 5 | * 6 | * Correct indent rule of this file 7 | * noexpandtab, shiftwidth=4, tabstop=8, softtabstop=0 8 | * 9 | */ 10 | (function() { 11 | "use strict"; 12 | 13 | function FizzBuzz(n) { 14 | let fizzbuzz = function (n) { 15 | let mod3 = n % 3 == 0 ? 'Fizz' : ''; 16 | let mod5 = n % 5 == 0 ? 'Buzz' : ''; 17 | 18 | return (mod3 + mod5) || n; 19 | }; 20 | for (let i=0; i