Thomas Mayfield bio photo

Thomas Mayfield

Polyglot programmer who loves the weird beautiful chaos of humans building software together. Fitness nerd. Southern kid living in Massachusetts.

Vim syntax highlighting for SQL strings inside Ruby code

Working in Rails, it’s not uncommon to have database queries that are better off expressed without using ActiveRecord. This usually means stuffing the SQL query itself into a string, and then running it against the database using ActiveRecord::Base.connection.execute. Unfortunately, these queries tend to be on the large and complex side, so having a big blob of un-highlighted SQL in the middle of your Ruby code isn’t the nicest thing.

However, there’s a loose convention on many Rails projects to denote such queries as heredoc strings starting with <<~SQL. I wanted to see if I could set up Vim to highlight that kind of heredoc in Ruby as SQL instead of a string. Turns out it’s possible!


Put the following in ~/.vim/after/syntax/ruby.vim

unlet b:current_syntax
syn include @SQL syntax/sql.vim
syn region sqlHeredoc start=/\v\<\<[-~]SQL/ end=/\vSQL/ keepend contains=@SQL
let b:current_syntax = "ruby"


Without syntax highlighting


With syntax highlighting


We’re using the vim after directory to run some extra syntax highlight rules for Ruby after the regular syntax/ruby.vim rules have been run. We load up the sql syntax rules into a syntax group called @SQL, then tell Vim that any region starting with a SQL heredoc string and ending with the terminating “SQL” should be highlighed by those imported syntax rules. Not bad for a couple of lines.

So what’s with the unlet / let business? Turns out that most Vimscript syntax files have a guard clause at the top that will bail out if b:current_syntax is already set (here’s the one from ruby.vim). I think this is to prevent double-sourcing of the files, but I’m not 100% sure. The practical effect here is that since the sql syntax file also contains such a guard, we needed to make sure that variable was unset so that we could properly execute that file for import here. I believe that’s what going on here, anyway—this is the point where I bailed out of digging further down the rabbithole.

Resources I found handy while figuring this out