Friday, October 31, 2008

Vim pr0n: Statusline flags for bundy whitespace

Lately I've been working on some projects with indenting so screwed you could print the source code out and wank with it and not be able to tell the difference. It's as though the author couldn't decide whether to use tabs or spaces, so they flipped a coin every time they began a new source file, and evidently the coin landed on its fucking edge more often than not since there are a fair number of files that use both tabs and spaces.

I've also developed an extreme anti-fetish for trailing whitespace recently. I made vim highlight it, and that shit is fucking everywhere!

ZOMG

So, in a bid to eradicate both mixed indenting and trailing whitespace from the universe, I've made vim display some flags on my statusline whenever they are present, and whenever my &expandtab setting is wrong. Read this raving to find out how.

Note: this raving assumes a fair amount of knowledge about statuslines in vim. If you aren't pro at statuslines then you should read this raving first.

Just so you know what I mean...


A buffer containing mixed indenting, and trailing whitespace (click to enlarge).


A buffer with &et set wrong, and trailing whitespace (click to enlarge).


The three statusline flags of interest in the above screenshots are:

  • [mixed-indenting]: Appears when a buffer is indented with both tabs and spaces

  • [&et]: Appears when a buffer is indented consistently (i.e. using only spaces or only tabs) but the &expandtab setting for the buffer is wrong.

  • [\s]: Appears when a buffer contains trailing whitespace


The code for the [mixed-indenting] and [&et] flags


This is snipped straight outta my vimrc:
 1 "display a warning if &et is wrong, or we have mixed-indenting
 2 set statusline+=%#error#
 3 set statusline+=%{StatuslineTabWarning()}
 4 set statusline+=%*
 5
 6 "recalculate the tab warning flag when idle and after writing
 7 autocmd cursorhold,bufwritepost * unlet! b:statusline_tab_warning
 8
 9 "return '[&et]' if &et is set wrong
10 "return '[mixed-indenting]' if spaces and tabs are used to indent
11 "return an empty string if everything is fine
12 function! StatuslineTabWarning()
13     if !exists("b:statusline_tab_warning")
14         let tabs = search('^\t', 'nw') != 0
15         let spaces = search('^ ', 'nw') != 0
16
17         if tabs && spaces
18             let b:statusline_tab_warning =  '[mixed-indenting]'
19         elseif (spaces && !&et) || (tabs && &et)
20             let b:statusline_tab_warning = '[&et]'
21         else
22             let b:statusline_tab_warning = ''
23         endif
24     endif
25     return b:statusline_tab_warning
26 endfunction

There are three parts to this code. The function (lines 12–26) works out what should be displayed on the statusline and caches the result. An autocommand on line 7 clears this cache when the user is idle or saves the file. Lines 2–4 put the flag on the statusline.

Note that the caching is needed as the function is called every time the statusline is refreshed. Without caching this would be very costly since the function searches the entire buffer for a leading space and a leading tab.

The code for the [\s] flag


Also straight from my vimrc, this code functions very similarly to the previous code. Lines 8–17 calculate and cache what should be displayed on the statusline. Line 4 clears this cache when the user is idle, or saves the file. Line 1 puts the flag on the statusline.
 1 set statusline+=%{StatuslineTrailingSpaceWarning()}
 2
 3 "recalculate the trailing whitespace warning when idle, and after saving
 4 autocmd cursorhold,bufwritepost * unlet! b:statusline_trailing_space_warning
 5
 6 "return '[\s]' if trailing white space is detected
 7 "return '' otherwise
 8 function! StatuslineTrailingSpaceWarning()
 9     if !exists("b:statusline_trailing_space_warning")
10         if search('\s\+$', 'nw') != 0
11             let b:statusline_trailing_space_warning = '[\s]'
12         else
13             let b:statusline_trailing_space_warning = ''
14         endif
15     endif
16     return b:statusline_trailing_space_warning
17 endfunction

An aside: use the list and listchars settings!


I have these lines in my vimrc:

set list
set listchars=tab:▷⋅,trail:⋅,nbsp:⋅

This tells vim to display tab characters as little arrow things and trailing spaces as floating dots (as in the two screenshots at the top of this raving). Having tabs displayed is invaluable as it becomes obvious how a file is indented, and if you aren't following suit.

Join in the fight against bundy whitespace today!


I was eavesdropping on two catholic priests yesterday* when I overheard one of them talking about people who mix tabs and spaces, saying: "... I know for a fact that they will spend their eternities in hell licking flaming shit nuggets out of satans ass crack while he jacks off furiously, periodically ejaculating streams of molten lava in their faces!"

On the other hand, studies show that people who indent code responsibly are far more likely to have sex with famous actresses than those who don't.

So what are you waiting for? Take every precaution to ensure you indent correctly! And fuck trailing whitespace!


* may not be true.

4 comments:

  1. I am a catholic priest - how dare you eavesdrop into my private conversations!!!1!one

    ReplyDelete
  2. Also see :he hl-SpecialKey to make tabs and trailing spaces stand out even more. I use it with:
    set listchars=tab:\ \ ,trail:·,extends:…,nbsp:‗
    to see tabs without using strange looking symbols.

    ReplyDelete
  3. The following command will solve all of the problems for you by simply deleting them.

    :perldo s/\s+$//; s/^ (\s*) (?=\S) / s#[^\t]##g;$_ /xe;

    ReplyDelete
  4. A mate of mine asked for some functions to jump to erroneous ^\s and \s$.

    I'm pretty amateur when it comes to Vim functions, but have at you:

    function! SeekIndentWarningOccurrence()
      if (!&et)
        /^
      elseif (&et)
        /^\t
      endif
      exe "normal 0"
    endfunction

    function! SeekTrailingWhiteSpace()
      let [nws_line, nws_col] = searchpos('\s\+$', 'nw')
      if ( nws_line != 0 )
        exe "normal ".nws_line."G"
        " This would be nicer, but | doesn't seem to collapse \t in to 1 col?
        " exe "normal ".nws_col."|"
        " so i'll do this instead :( Might be a better way
        exe "normal 0"
        exe "normal ".(nws_col-1)."l"
      endif
    endfunction


    Excuse the abuse of and-en-bee-ess-pees for indentation.

    ReplyDelete