aboutsummaryrefslogtreecommitdiff
path: root/autoload/detect_indent.vim
blob: 71a43e87c22812c93396de75fc12a9b5f9190409 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
" 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

  " 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 the value for 'expandtab' as determined by the user's configuration
  " matches the expected dominance of space-indented lines over tab-indented
  " lines, we're already using the right setting, and we stop here
  "
  if &expandtab == (spaces['total'] >= tabs)
    return
  endif

  " If 'expandtab' is set, 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
    setlocal noexpandtab softtabstop=0
    let &l:shiftwidth = &tabstop

  " If 'expandtab' is not set, we need to set it, and guess an appropriate
  " 'shiftwidth'.
  else
    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

  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