Post hero cover

How to program Vim using Ruby

I recently started working in an Angular project where I’m not able to use vim-projectionist to navigate the code. vim-projectionist is awesome, and I’ve grown used to it. Searching for files by name, in a code base with hundreds of files, started to become painful and I needed an alternative. Because I couldn’t find a plug-in that satisfied my needs, I decided to do it myself. In this article, I hope to demonstrate how you can program Vim using Ruby.

Let’s say that you have an HTML file that looks like this:

...
<div class="timepicker">
  <timepicker>
    ...
  </timepicker>
</div>
...

When the cursor is on line two or four (the timepicker tag), you want to trigger a shortcut that opens the JavaScript file for the directive timepicker.

Ruby to the rescue

In this project, every directive is in the folder javascript/directives/, where the directive’s name is also the file’s name. If the name has more than one word, such as chart-timepicker, the file name would be snake case: chart_timepicker.

I’m assuming you don’t know enough VimScript to make this work (I don’t), but fear not, if you compiled Vim with Ruby support, this will be enough:

function! AngularTemplateToDirective()
ruby << EOF
  @buffer = VIM::Buffer.current # get current bufer
  match = @buffer.line.match(/<\/?([\w-]+)/) # match current line

  if match
    directive = match[1].gsub("-", "_") # get directive's file name
    VIM.command(":e javascript/directives/#{directive}.js") # open file
  end
EOF
endfunction

autocmd BufRead,BufNewFile *assets/templates*.html nnoremap <silent> <C-]> :call AngularTemplateToDirective()<cr>

It’s a Vim function that you can trigger with C-] or call with :call AngularTemplateToDirective() (or map to a shortcut). Notice that it uses a module called VIM on line three. That module comes from Vim and it allows us to control it from the Ruby. If you want to learn more use :help ruby inside Vim.

This function fetches the current buffer and the current line. If the line matches an HTML tag (something like <timepicker> , </timepicker> or <timepicker something>), it takes the tag’s name, turns it to snakecase, and opens the corresponding directive’s file.

Cool right? Five years using Vim and there’s still so much to learn!

A little more advanced

With the function above, you can open directives from the HTML. How nice would it be to open CSS files from the HTML as well?

Taking the previous HTML example, when I’m in a line with class="timepicker" I want to open the BEM component named timepicker that, by convention, sits in stylesheets/components/timepicker.scss.

Building on the previous implementation, you first check if the current line contains class=", if not then look for the tag using the code from before. If it does match, you extract the component’s name from the class, turn it into snake case and open the file:

function! AngularTemplateToDirectiveOrCSS()
ruby << EOF
  @buffer = VIM::Buffer.current

  if @buffer.line.include?("class=")
    match = @buffer.line.match(/class="([\w-]+)"/)

    if match
      file = match[1].split("__")[0].split("--")[0].gsub("-", "_")
      VIM.command(":e app/assets/stylesheets/application_nsx/components/#{file}.scss")
    end
  else
    match = @buffer.line.match(/<\/?([\w\-]+)/)

    if match
      directive = match[1].gsub("-", "_")
      VIM.command(":e app/assets/javascripts/angular/directives/#{directive}.js")
    end
  end
EOF
endfunction

I know that this code could be better, but I’m not concerned with that because it’s part of my Vim configuration, I’m always changing it, and it actually works!

I hope that you’ve found something new in this blog post. If you’re looking for more ideas for your Vim setup, check out this blog post from Miguel and subscribe to our blog. There are many Vim users at Subvisual and we always publish new content.

Gabriel Poça

About Gabriel Poça

There is no time for introductions, but if there was, you would instantly understand that this guy is a developer and he loves every aspect of it.
Like what you've read so far? Let's work together.