File: preproc_indent.vim

package info (click to toggle)
vim 2%3A9.1.2103-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 93,456 kB
  • sloc: ansic: 433,730; cpp: 6,399; makefile: 4,597; sh: 2,397; java: 2,312; xml: 2,099; python: 1,595; perl: 1,419; awk: 730; lisp: 501; cs: 458; objc: 369; sed: 8; csh: 6; haskell: 1
file content (148 lines) | stat: -rw-r--r-- 4,598 bytes parent folder | download
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
vim9script
# Script to fix preprocessor indentation in Vim's C source code.
#
# Usage: Vim -S <this-file>
#
# Specifications:
# - If there is no indentation on the line containing the preprocessor
#   directive (`#`) following the first `#if~`, the indentation amount is
#   `nesting level - 1` spaces. Otherwise, the indentation amount is `nesting
#   level` spaces.
# - However, if a preprocessor directive line is detected after the
#   corresponding `#endif` of the above `#if~`, the indentation amount is
#   fixed at `nesting level` and the line is reprocessed from the first line.
# - If the preprocessor directive line ends with a line continuation (`\`) and
#   the next line is blank, the line continuation (`\`) and the next line are
#   deleted.
#
# Author: Hirohito Higashi (@h-east)
# Last Update: 2026 Jan 12

def Get_C_source_files(): list<string>
  var list_of_c_files: list<string> = []
  if empty(list_of_c_files)
    var fpath = '../../src'
    var list = glob(fpath .. '/*.[ch]', 0, 1) + [fpath .. '/xxd/xxd.c']
    # Some files are auto-generated, so skip those
    list_of_c_files = filter(list, (i, v) => v !~ 'dlldata.c\|if_ole.h\|iid_ole.c')
  endif
  return list_of_c_files
enddef

def FixPreprocessorIndent(fname: string)
  execute 'edit! ' .. fname

  var nest: number = 0
  var indent_offset: number = 0  # -1 if whole-file guard detected
  var first_if_seen: bool = false
  var offset_determined: bool = false
  var whole_file_guard_ended = false

  # First pass: remove trailing backslash + empty next line
  var lnum = 1
  while lnum <= line('$')
    var line: string = getline(lnum)
    if line =~# '^\s*#.*\\$'
      var next_line: string = getline(lnum + 1)
      if next_line =~# '^\s*$'
        # Remove backslash from current line and delete next line
        setline(lnum, substitute(line, '\s*\\$', '', ''))
        deletebufline('%', lnum + 1)
        continue  # Don't increment, check same line again
      endif
    endif
    lnum += 1
  endwhile

  # Second pass: fix preprocessor indent
  while true
    var is_reprocess: bool = false
    for l in range(1, line('$'))
      var line: string = getline(l)

      # Skip if not a preprocessor directive
      if line !~# '^\s*#'
        continue
      endif

      # Extract directive and current indent
      var match_li: list<string> = matchlist(line, '^\(\s*\)#\(\s*\)\(\w\+\)')
      if empty(match_li)
        continue
      endif
      var cur_spaces: string = !empty(match_li[1]) ? match_li[1] : match_li[2]
      var directive: string = match_li[3]

      # If indent_offset != 0 but we encounter indented #, it's not whole-file
      # guard. Reprocess from line 1 with indent_offset=0
      if whole_file_guard_ended && offset_determined && indent_offset != 0
        indent_offset = 0
        nest = 0
        is_reprocess = true
        break
      endif

      # After first #if, determine offset from first nested directive
      # Only check if # is at column 1 (no leading spaces)
      if first_if_seen && !offset_determined
        offset_determined = true
        if empty(cur_spaces)
          # No indent after first `#if` --> whole-file guard style
          indent_offset = -1
        endif
      endif

      # Determine expected indent based on directive type
      var expected_indent: number

      if directive ==# 'if' || directive ==# 'ifdef' || directive ==# 'ifndef'
        if !first_if_seen
          first_if_seen = true
        endif
        expected_indent = nest + indent_offset
        nest += 1
      elseif directive ==# 'elif' || directive ==# 'else'
        expected_indent = nest - 1 + indent_offset
      elseif directive ==# 'endif'
        nest -= 1
        if nest <= 0
          # Reset for next top-level block (but keep offset_determined)
          nest = 0
          whole_file_guard_ended = true
        endif
        expected_indent = nest + indent_offset
      else
        # Other directives (#define, #include, #error, #pragma, etc.)
        expected_indent = nest + indent_offset
      endif

      if expected_indent < 0
        expected_indent = 0
      endif

      # Build expected line
      var rest = substitute(line, '^\s*#\s*', '', '')
      var expected_line: string
      expected_line = '#' .. repeat(' ', expected_indent) .. rest

      # Update line if different
      if line !=# expected_line
        setline(l, expected_line)
      endif
    endfor

    if !is_reprocess
      break
    endif
  endwhile

  update
enddef

# Main
for fname in Get_C_source_files()
  FixPreprocessorIndent(fname)
endfor

qall!
# vim: et ts=2 sw=0