Monday, August 17, 2009

Vim -- :TOhtml & Customization

When I first started this blog, I was surprised (and disappointed) that Google's Blogger platform didn't have some nice built-in feature for code formatting and syntax highlighting. I experimented with a number of third-party solutions, but didn't find anything that I particularly liked, so my posts have had some ugly-looking code in them. Until now.

...Well, until my previous post, anyway.

A then-coworker told me a while back about vim's :TOhtml feature, but it wasn't until last night that I actually checked it out. As it turns out, it was just what I was looking for. Out of the box, it's a little cumbersome for my purposes, but a little vim scripting helped a lot.

Default Behavior

To start, I set a few options in my .vimrc:
let html_use_css = 1 " Use stylesheet instead of inline style
let html_number_lines = 0 " don't show line numbers
let html_no_pre = 1 " don't wrap lines in <pre>


I also choose a vim color scheme I like (wombat). You can look through examples of tons of schemes here. (I spent a while trying to figure out why I couldn't set the color scheme (after I downloaded it to .vim/colors/), and the answer I found was that color schemes only work in gVim.)

So, having set some options in my .vimrc, and chosen a colorscheme in gVim, calling :TOhtml on
class Person
  attr_accessor :age

  def speak
    "Hi!"
  end

  def say_age
    "I am this many: #{'|' * @age}"
  end
end


gives:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title>~/demo.rb.html</title>
    <meta name="Generator" content="Vim/7.2">
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <style type="text/css">
      <!--
      .Identifier { color: #cae682; }
      .Type { color: #cae682; }
      .Statement { color: #8ac6f2; }
      .Constant { color: #e5786d; }
      .Function { color: #cae682; }
      .Special { color: #e7f6da; }
      .String { color: #95e454; font-style: italic; }
      body { color: #f6f3e8; background-color: #242424; font-family: monospace; }
      .PreProc { color: #e5786d; }
      -->
    </style>
  </head>
  <body>
    <span class="PreProc">class</span>&nbsp;<span class="Type">Person</span><br>
    &nbsp;&nbsp;<span class="Statement">attr_accessor</span>&nbsp;<span class="Constant">:age</span><br>
    <br>
    &nbsp;&nbsp;<span class="PreProc">def</span>&nbsp;<span class="Function">speak</span><br>
    &nbsp;&nbsp;&nbsp;&nbsp;<span class="Special">&quot;</span><span class="String">Hi!</span><span class="Special">&quot;</span><br>
    &nbsp;&nbsp;<span class="PreProc">end</span><br>
    <br>
    &nbsp;&nbsp;<span class="PreProc">def</span>&nbsp;<span class="Function">say_age</span><br>
    &nbsp;&nbsp;&nbsp;&nbsp;<span class="Special">&quot;</span><span class="String">I am this many: </span><span class="Special">#{</span><span class="Special">'</span><span class="String">|</span><span class="Special">'</span>&nbsp;* <span class="Identifier">@age</span><span class="Special">}</span><span class="Special">&quot;</span><br>
    &nbsp;&nbsp;<span class="PreProc">end</span><br>
    <span class="PreProc">end</span><br>
  </body>
</html>



That's pretty good. If your goal was a stand-alone HTML file you'd be done, but for paste-ready output we need a little vim scripting help.

Altered Behavior

Instead of wrapping the output in a full html page, I really just want it wrapped in a div. I also want to strip out some "extra" (according to Blogger's HTML interpreter) line breaks, and pull out the CSS definitions (for later). To accomplish this, I've put together a simple vim script, which defines a new method :DivHtml
function! DivHtml(line1, line2)
  exec a:line1.','.a:line2.'TOhtml'
  %g/<style/normal $dgg
  %s/<\/style>\n<\/head>\n//
  %s/.vim_block {/.vim_block {/
  %s/<body\(.*\)>\n/<div class="vim_block"\1>/
  %s/<\/body>\n<\/html>/<\/div>
  %s/<br>//g

  set nonu
endfunction
command -range=% DivHtml :call DivHtml(<line1>,<line2>)


Now, calling :DivHtml on the ruby code above gives code I can just cut & paste into the 'Edit Html' Blogger window:
<!--
.Identifier { color: #cae682; }
.Type { color: #cae682; }
.Statement { color: #8ac6f2; }
.Constant { color: #e5786d; }
.Function { color: #cae682; }
.Special { color: #e7f6da; }
.String { color: #95e454; font-style: italic; }
.vim_block { color: #f6f3e8; background-color: #242424; font-family: monospace; }
.PreProc { color: #e5786d; }
-->
<div class="vim_block"><span class="PreProc">class</span>&nbsp;<span class="Type">Person</span>
  &nbsp;&nbsp;<span class="Statement">attr_accessor</span>&nbsp;<span class="Constant">:age</span>

  &nbsp;&nbsp;<span class="PreProc">def</span>&nbsp;<span class="Function">speak</span>
  &nbsp;&nbsp;&nbsp;&nbsp;<span class="Special">&quot;</span><span class="String">Hi!</span><span class="Special">&quot;</span>
  &nbsp;&nbsp;<span class="PreProc">end</span>

  &nbsp;&nbsp;<span class="PreProc">def</span>&nbsp;<span class="Function">say_age</span>
  &nbsp;&nbsp;&nbsp;&nbsp;<span class="Special">&quot;</span><span class="String">I am this many: </span><span class="Special">#{</span><span class="Special">'</span><span class="String">|</span><span class="Special">'</span>&nbsp;* <span class="Identifier">@age</span><span class="Special">}</span><span class="Special">&quot;</span>
  &nbsp;&nbsp;<span class="PreProc">end</span>
  <span class="PreProc">end</span>
</div>


(which renders as the ruby snippet I pasted above)

The only part of the process that is still kinda annoying is that you have to copy/paste the CSS into your header or external CSS file. Both :TOhtml and :DivHtml only output the CSS definitions for styles actually used in a snippet, so it's up to the user to check if all the definitions in a new snippet are already in your layout, or if you need to add them. The alternative would be to use inline CSS (:help :TOhtml), but that's ugly.

To finish things off, I gave the wrapping div the vim_block class, and gave that a background-color and border in my header, to make it look a little nicer. I also got rid of the "font-family: monospace" bit, because I thought it made the code hard to read.

Other than manually checking the CSS, it's a very painless process to put code snippets on the blog, and I'm happy with the look and feel of them.

4 comments:

nevans said...

Very nice. One (minor) downside of using CSS vs. inline styles is that the coloration doesn't make it through to feed readers. But I'm used to viewing the original when code is present, so that isn't such a big deal.

nevans said...

Also, you seem to have lost the indentation (non-breaking spaces) in your ruby example. perhaps retaining the pre would solve that?

Patrick Schless said...

nevans -- Thanks for the heads up about the indentation. I had written this post as I developed my vimrc, and the snippet in question actually had been generated with html_no_pre unset. For some reason, not setting that option means you won't get nbsp's for spaces (and the css doesn't manage indentation, either). I regenerated that snippet with the version of my vimrc represented in the post, and now it looks better :)

As for the the lack of CSS in readers, I hear ya. I'm ok with it, though. I'm like you -- if a post in Google Reader looks funky, I just click on the link.

Patrick Schless said...

nevans -- Oh, after regenerating the snippets, RSS readers now correctly show the indentation (because I'm now using nbsp's). It doesn't have colors, but I think it's much more readable now (with indentation) that it was before.

Post a Comment