-.- --. .-. --..

Vim-fu Promote to fetch in Ruby

08 Dec 2013

I use the PromoteToLet function a lot. I saw it first on a screencast by Gary Bernhardt. The function is also available on his public vimrc. I tried out some changes to it. The PromoteToLet function in Vim converts a variable declaration such as user = User.new into let(:user) { User.new }. Although the pratice of using lets instead of plain variables is now a common practice, it was not the case always and so, this function maps the <leader>p command to replace the variable-style declaration to the let-style one. Here’s one that converts a [] based Ruby hash access syntax to the much safer Hash#fetch.

Introduction

First up, let’s understand the original:

1 function! PromoteToLet()
2     :normal! dd
3     :normal! P
4     :.s/\(\w\+\) = \(.*\)$/let(:\1) { \2 }/
5     :normal ==
6 endfunction
7 :command! PromoteToLet :call PromoteToLet()
8 :map <leader>p :PromoteToLet<cr>

The : before all the commands inside the function..endfunction block is Vim’s way of understanding that the declaration is an ex command. Ex is the command-line mode of Vim. The 2nd line, :normal! dd is equivalent to In normal mode, delete one line. dd, if you don’t know, is equivalent to delete one line. More on Vim motions. The 3rd line specifies Vim to paste the deleted text on the same line as the cursor. If a plain p was used, it would add new line and then paste the contents. The 4th line contains the regex substitution command .s/<text to search>/<text that will replace the result>. Note that the regex above is specific to Vim versions less that 7.4. If you’re using the latest version, the 4th line should be:

:.s/(\w+) = (.*)$/let(:\1) { \2 }/

The . before the substitution command and after the colon specifies that the substitution should run only for that one line. And finally, the == instruction indents the line.

Ruby Hash access

Ruby has many ways to access a key-value pair in a Hash object. One of the most used method is the [] method. Yes, [] is a method. And there are quite a few articles on why this might a not-so-good idea everytime and that, using Hash#fetch is a much safer alternative. Going to each line and modifying the syntax, I wrote a function that will make this conversion much easier:

function PromoteToFetch()
  :normal! dd
  :normal! P
  :.s/\v(\w+)\[(:?\w+)\]/\1\.fetch\(\2\)/
  :normal ==
endfunction
command! -range PromoteToFetch <line1>,<line2>:call PromoteToFetch()
map <leader>c :PromoteToFetch<cr>

By hitting the <leader>c combination in normal mode, this function changes a statement like args[:user] to args.fetch(:user). In order to change all instances in the file, this command can be run in visual mode after selecting the entire file.

The only difference from the PromoteToLet variant, apart from the substitution syntax is, the -range operator for the command! declaration. This operator makes it possible for the function to work on ranges that are selected in the visual mode whereas the PromoteToLet variant works only for a single line by design. Add this to your ~/.vimrc and try it on your own.