Pod::Plexus meta-yak, editor folding

Pod::Plexus development has led me down a slippery slope peppered with boulders and broken glass I thought I could avoid forever.

My inner typographer wants to lay out documentation "just so", which means putting all the POD together, usually at the end of the file. Pod::Plexus and Pod::Weaver move POD around, making this semitypographical exercise a big time waster. All the careful consideration of placing "SEE ALSO" before (or after) "BUGS" matters naught.

Suddenly interleaving POD and code doesn't sound so bad. It doesn't change the finished product. I can also get rid of some of my pre-method comments, since the documentation will do double duty. Smaller distributions and less work for me? What's not to like?

Well, it's a pity that interleaved code and POD is butt ugly and distracting. By breaking up the continuity and context between methods, it's actually interfering with my ability to hold design in my head. And this is code I'm in the process of writing. Maybe I'll evolve out of my modalities, but it's a learning hurdle for now.

Which leads me to my other nemesis, source folding. If you don't know, it's where groups of lines in a source file are squashed ("folded") together into a single line. As much as I hate the concept, the ability to hide POD or code might make interleaving them barely tolerable. It's ironic because I hate source folding about as much as I hate interleaving code and POD. Necessity seems to be the mother of strange bedfellows. Maybe two wrongs can make a right.

So I spent the past couple nights hacking POD/code folding into my .vimrc. Now two keystrokes will hide all the POD so I can read the code, and two other keystrokes will swap that around. The results are ugly and preliminary, but I'm kind of happy about them. I thought I'd share. This is me sharing. You have been shared with.

I plan to clean this up and add it to Pod::Plexus' CPAN distribution after the module is ready to be released. I chose to shave this meta-yak now because I think it'll help me write the remaining code faster. I'll let you know how that works out for me.

Meanwhile, if you know Vim scripting, you're probably better off than I am. Patches to this are welcome. For example, I don't know how I'm going to add Moose syntax to the mix.

" Folding!  Ogods, the FOLDING!

let perl_include_pod=1
let perl_extended_vars=1
let perl_sync_dist=1000

let perl_fold=1
let perl_nofold_packages=1

syntax on

if has("gui_running")
  colorscheme rocco
  set guioptions-=T
else
  colorscheme rocco
endif

" Highlight whitespace where it doesn't belong.  Goes after "syntax on".
highlight ExtraWhitespace ctermbg=red guibg=red
au ColorScheme * highlight ExtraWhitespace guibg=red
au BufEnter    * match     ExtraWhitespace /\s\+$/
au InsertEnter * match     ExtraWhitespace /\s\+\%#\@<!$/
au InsertLeave * match     ExtraWhiteSpace /\s\+$/

" Extend the stock perl.vim highlighting without hacking a custom one.
au Syntax perl call MyPerlAdd()
function MyPerlAdd()
  syn match perlPodTabs "\t" contained
  syn cluster Pod add=perlPodTabs
  " syn match podVerbatimLine "^\s.*$" contains=perlPodTabs,@NoSpell
  syn keyword perlTodo TODO TBD FIXME XXX FUTURE NOTE NB contained
  syn match perlTodo "-><-" contained
  hi link perlPodTabs ExtraWhitespace

  " Extend POD regions to include Pod::Plexus syntax.
  syn match podCommand "^=public"    nextgroup=podCmdText skipwhite contains=@NoSpell keepend
  syn match podCommand "^=private"   nextgroup=podCmdText skipwhite contains=@NoSpell keepend
  syn match podCommand "^=attribute" nextgroup=podCmdText skipwhite contains=@NoSpell keepend
  syn match podCommand "^=method"    nextgroup=podCmdText skipwhite contains=@NoSpell keepend

  syn sync match perlSyncPOD grouphere perlPOD "^=public"
  syn sync match perlSyncPOD grouphere perlPOD "^=private"
  syn sync match perlSyncPOD grouphere perlPOD "^=attribute"
  syn sync match perlSyncPOD grouphere perlPOD "^=method"

  syn region perlPOD start="^=public" end="^=cut" contains=@Pod,@Spell,perlTodo keepend fold
  syn region perlPOD start="^=private" end="^=cut" contains=@Pod,@Spell,perlTodo keepend fold
  syn region perlPOD start="^=attribute" end="^=cut" contains=@Pod,@Spell,perlTodo keepend fold
  syn region perlPOD start="^=method" end="^=cut" contains=@Pod,@Spell,perlTodo keepend fold

  syn match podSpecial "\[%[^%\]]*%\]" contains=@NoSpell
endfun

highlight Folded guibg=black guifg=darkred
highlight FoldColumn guibg=black guifg=darkred

" z1 shows POD; z2 shows everything else.  TODO - Support Moose syntax.
nnoremap <silent> z1 :call ShowFoldsMatching(hlID('perlPOD'), 0)<CR>
nnoremap <silent> z2 :call ShowFoldsMatching(hlID('perlSubFold'), 0)<CR>

function ShowFoldsMatching(sid, negate)
  " Save the bell status, and turn off visual and audible bells.
  let s:saved_vb = &vb || 0
  let s:saved_tvb = &t_vb || ''
  let &vb = 1
  let &t_vb = ''

  " Remember the cursor's position in the file.
  let s:save_cursor = winsaveview()
  let s:save_winline = winline()

  " Top of the file, and close all folds.
  normal gg
  normal zM
  let s:lastline = -1

  " Iterate all folds, opening the ones we want.
  if (a:negate)
    while s:lastline != line('.')
      if count(synstack(line('.'), col('.')), a:sid) < 1
        normal zo
      endif
      let s:lastline = line('.')
      normal zj
    endwhile
  else
    while s:lastline != line('.')
      if count(synstack(line('.'), col('.')), a:sid) > 0
        normal zo
      endif
      let s:lastline = line('.')
      normal zj
    endwhile
  endif

  " Restore the cursor's position on the line.
  call winrestview(s:save_cursor)
  unlet s:save_cursor

  " Restore the cursor's line on the screen.
  let s:winline_off = winline() - s:save_winline

  while (s:winline_off < 0)
    " <C-Y>
    exec "normal \x19"
    let s:winline_off = s:winline_off + 1
  endwhile

  while (s:winline_off > 0)
    " <C-E>
    exec "normal \x05"
    let s:winline_off = s:winline_off - 1
  endwhile

  unlet s:save_winline
  unlet s:winline_off

  " Restore audible and visual bell.
  let &vb = s:saved_vb
  let &t_vb = s:saved_tvb
  unlet s:saved_vb
  unlet s:saved_tvb
endfunction

4 Comments

Your writings are as amusing as they are insightful. This is a compliment, you have been complimented. :)

When you first look at it Literate programming seems like a good idea - then you try it. After which it still seems like a good idea but is (much) harder work than anticipated.

I've had a few goes - the best editor I've used so far is leo:
http://webpages.charter.net/edreamleo/front.html
It is tres cool despite the Python influence (JOKE! Python is great).Trouble is of course you are then adding another tool dependency to your kitbag

The question I've never been able to satisfactorily answer is "Who exactly am I going to this trouble for?"

Is it:
Me now to help me understand why I am trying to solve this problem in such an unforgivably idiosyncratic way?
Me in the future to help me realise there was a much easier way?
Someone else who neither knows nor cares why I did things this way?

Personally I've found that I can create concisely structured and eloquently commented literate programs that still have to be explained to others with bubbles, arrows, and frequent coffee breaks. So the "literacy" turns out to be more worthwhile for me than A.N Other.

Leave a comment

About Rocco Caputo

user-pic Among other things I write software, a lot of which is in Perl.