aboutsummaryrefslogtreecommitdiff
path: root/autoload/strip_trailing_whitespace.vim
blob: 17dec7a33984e6582f7ec7ddfa35161ac102b913 (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
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
" Set the pattern for trailing horizontal whitespace to match and remove.  The
" `[:space:]` character class suffices for almost everything in practice, but
" at the time of writing it still only includes ASCII characters.  I'm writing
" this because I had a document with lines with a trailing NO-BREAK SPACE
" (U+00A0) which `[:space:]` doesn't catch, so we'll round out the collection
" by adding all the Unicode space characters, since that's easy to do.
"
" <https://jkorpela.fi/chars/spaces.html>
" archived: <http://web.archive.org/web/20230318105634/https://jkorpela.fi/chars/spaces.html>
"
" * U+0020: SPACE
" * U+00A0: NO-BREAK SPACE
" * U+1680: OGHAM SPACE MARK
" * U+180E: MONGOLIAN VOWEL SEPARATOR
" * U+2000: EN QUAD
" * U+2001: EM QUAD
" * U+2002: EN SPACE
" * U+2003: EM SPACE
" * U+2004: THREE-PER-EM SPACE
" * U+2005: FOUR-PER-EM SPACE
" * U+2006: SIX-PER-EM SPACE
" * U+2007: FIGURE SPACE
" * U+2008: PUNCTUATION SPACE
" * U+2009: THIN SPACE
" * U+200A: HAIR SPACE
" * U+200B: ZERO WIDTH SPACE
" * U+202F: NARROW NO-BREAK SPACE
" * U+205F: MEDIUM MATHEMATICAL SPACE
" * U+3000: IDEOGRAPHIC SPACE
" * U+FEFF: ZERO WIDTH NO-BREAK SPACE
"
let s:pattern
      \ = '['
      \ . '[:space:]'
      \ . '\u0020'
      \ . '\u00A0'
      \ . '\u1680'
      \ . '\u180E'
      \ . '\u2000-\u200B'
      \ . '\u202F'
      \ . '\u205F'
      \ . '\u3000'
      \ . '\uFEFF'
      \ . ']\+$'

" Wrapper function to strip both horizontal and vertical trailing whitespace,
" return the cursor to its previous position, and report changes
function! strip_trailing_whitespace#(start, end) abort

  " Save cursor position
  let pos = getpos('.')

  " Whether we made changes
  let changed = 0

  " If we're going to the end, strip vertical space; we do this first so we
  " don't end up reporting having trimmed lines that we deleted
  if a:end == line('$')
    let vertical = s:StripVertical()
    let changed = changed || vertical > 0
  endif

  " Strip horizontal space
  let horizontal = s:StripHorizontal(a:start, a:end)
  let changed = changed || horizontal > 0

  " Return the cursor
  call setpos('.', pos)

  " Report what changed
  let msg = horizontal.' trimmed'
  if exists('vertical')
    let msg = msg.', '.vertical.' deleted'
  endif
  echomsg msg

  " Return whether anything changed
  return changed

endfunction

" Strip horizontal trailing whitespace, return the number of lines changed
function! s:StripHorizontal(start, end) abort

  " Start a count of lines trimmed
  let stripped = 0

  " Iterate through buffer
  let num = a:start
  while num <= line('$') && num <= a:end

    " If the line has trailing whitespace, strip it off and bump the count
    let line = getline(num)
    if line =~# s:pattern
      call setline(num, substitute(line, s:pattern, '', ''))
      let stripped = stripped + 1
    endif

    " Bump for next iteration
    let num = num + 1

  endwhile

  " Return the number of lines trimmed
  return stripped

endfunction

" Strip trailing vertical whitespace, return the number of lines changed
function! s:StripVertical() abort

  " Store the number of the last line we found with non-whitespace characters
  " on it; start at 1 because even if it's empty it's never trailing
  let eof = 1

  " Iterate through buffer
  let num = 1
  while num <= line('$')

    " If the line has any non-whitespace characters in it, update our pointer
    " to the end of the file text
    let line = getline(num)
    if line =~# '\S'
      let eof = num
    endif

    " Bump for next iteration
    let num = num + 1

  endwhile

  " Get the number of lines to delete; if there are any, build a range and
  " remove them with :delete, suppressing its normal output (we'll do it)
  let stripped = line('$') - eof
  if stripped
    let range = (eof + 1).',$'
    silent execute range.'delete'
  endif

  " Return the number of lines deleted
  return stripped

endfunction