2

Suppose I've copied a bunch of lines of boilerplate code that I need to copy multiple times over, and then change in two separate places, in the same way:

  it 'includes sessions#new' do
    expect(get: '/sign_in').to route_to('devise/sessions#new')
  end

so I'm gonna copy this seven or eight times, and I need to change sessions#new on both lines. So if I go through and change the first line of each statement manually, I end up with this:

  it 'includes sessions#new' do
    expect(get: '/sign_in').to route_to('devise/sessions#new')
  end

  it 'includes sessions#destroy' do
    expect(get: '/sign_in').to route_to('devise/sessions#new')
  end

  it 'includes passwords#new' do
    expect(get: '/sign_in').to route_to('devise/sessions#new')
  end

  it 'includes passwords#create' do
    expect(get: '/sign_in').to route_to('devise/sessions#new')
  end

Now, I'd like to change all the second-line instances of sessions#new to the corresponding string from the previous line.

I know I can do this with a long, convoluted multi-line substitution command:

:%s/^\(  it 'include \)\(\w\+#\w\+\)\(.*' do\n.*'\)\(\w\+#\w\+\)\(.*\)$/\1\2\3\2\5

What I'd like to know is if it's possible to do something comparable and perhaps a little more compact with the :global command, like this:

:g/includes \(\w\+#\w\+\)/+1s/sessions#new/\1

Unfortunately, the above command doesn't work, because the \1 capture group isn't available to the :g command (and anyway, the :s command probably thinks it's for it).

Is there any way to get this done, or am I stuck with Option 1?

1

It is not possible, to capture groups in the :g command and then run an :s command, that uses those captured groups. You would have to do it manually, something like this perhaps:

g/includes/exe "norm! yi'"|+s/sessions#new/\=split(getreg('"'))[1]/

This uses the :g command to mark all "includes" lines and on each include line, it will use the i' textobject to capture the quoted string and copy it into the unnamed register. Then on the following line, it will execute an :s command, search for sessions#new and replaces it with the second word in the unnamed register. (using split() to access only the second half of the whitespace separated part in the register)

2

Well, for given sample, the substitute command can be simplified

:%s,\vincludes ([^']+).*\n.*/\zs[^']+,\1
  • , is used as delimiter here, so that / doesn't need escaping
  • \v is very magic, so that capturing groups and + don't need escaping
  • \zs set start of match, so that string up to that need not be captured and back-referenced
  • [^']+ will capture all non ' characters
  • .*/ will consume everything up to last / in the line as * is greedy

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.