5

I never made any plugins but I would like to get some advice for a specific case.

I'm writing a lot of ruby. Sometimes I have very long lines that I want to split.

Example:

MyModel.first_method.second_method(arg).third_method(arg.method)

to

MyModel
  .first_method
  .second_method(arg)
  .third_method(arg.method)

For the moment I have only the macro with f.i<CR><ESC>w but it fails when there is a dot inside parentheses. And I have to count how many times I want the macro to run.

I'm wondering what is the best way to optimise this ?

  • Search for a regex that will avoid searching dot inside regex?
  • For the moment I have only that :s/\./\1\r./g but my text is not indented and it doesn't deal with dot inside parentheses.

Thanks in advance for your help.

4

I'm not sure it will work for all the cases, but it seems to work on the example you provided:

:s/\v\ze(\(\w+)@<!\./\r  /g

The pattern used by the substitution command is: \v\ze(\(\w+)@<!\.

  • \v is the very magic switch (useful to get rid of backslashes)
  • \ze describes the end of the match. Since \ze is at the beginning of the pattern, it means that nothing will be substituted/removed. The replacement part of the substitution command will simply be inserted in front of the match.
  • (\(\w+)@<! describes an open parenthesis and a word (\(\w+) which must not precede what will follow in the pattern (@<!). See :h /\@<! for more info on this, or google negative lookbehind.
  • \. describes a dot

Whenever this pattern is found, :s inserts a newline and 2 spaces.


Edit: I've just realised it would fail if there are several dots inside a pair of parentheses. For example, if you have:

MyModel.first_method.second_method(arg).thrid_method(arg.method.foo.bar)

The previous substitution command will give:

MyModel
  .first_method
  .second_method(arg)
  .thrid_method(arg.method
  .foo
  .bar)

In this case, the following one could be more robust:

:s/\v\ze(\(\w+(\.\w+)*)@<!\./\r  /g

It should give:

MyModel
  .first_method
  .second_method(arg)
  .thrid_method(arg.method.foo.bar)

It's the same command as before, except this time, in your negative lookbehind, after the open parenthesis and a word, you add zero or more dots and words: (\.\w+)*


For the indentation problem, you could try this:

:s/\v\ze(\(\w+(\.\w+)*)@<!\./\="\n  " . matchstr(getline('.'), '^\s*')/g

It's, again, the same command, but this time the replacement part is the evaluation of an expression (\=) which returns a newline, 2 spaces and the level of indentation of the current line (matchstr(getline('.'), '^\s*')).

If you want to first remove a level of indentation on the line and remove 2 spaces, you could use :s/^ //, which would give:

:s/^  // | s/\v\ze(\(\w+(\.\w+)*)@<!\./\="\n  " . matchstr(getline('.'), '^\s*')/g

Edit2: In the previous command () is used to apply the multi * to the sub-pattern\.\w+ and @<! to \(\w+(\.\w+)*. But in the replacement part, you don't use any backreference (\1, ..., \9). So, to make the regex a little bit faster, you could use %() instead (:h /\%():

\%(\)   A pattern enclosed by escaped parentheses.  */\%(\)* */\%(* *E53*
    Just like \(\), but without counting it as a sub-expression.  This
    allows using more groups and it's a little bit faster.
    {not in Vi}

It would give:

:s/^  // | s/\v\ze%(\(\w+%(\.\w+)*)@<!\./\="\n  " . matchstr(getline('.'), '^\s*')/g

Edit3: To turn this command into a mapping, you could add this into your vimrc:

nnoremap <Leader>a :s/^  // <Bar> s/\v\ze%(\(\w+%(\.\w+)*)@<!\./\="\n  " . matchstr(getline('.'), '^\s*')/g<CR>

In the following code:

class MyMethods
  def mymethod
    MyModel.first_method.second_method(arg).thrid_method(arg.method)
  end
end

... if you hit <Leader>a while the cursor is on the third line, it should give you:

class MyMethods
  def mymethod
  MyModel
    .first_method
    .second_method(arg)
    .thrid_method(arg.method)
  end
end
  • Thanks a lot. Tricky regex. Do you if it's possible to indent each line ? No need for extra spaces in `\r ` then. – Mio May 11 '16 at 10:46
  • Sorry, I'm not sure to understand, do you mean the substitution command should indent the lines beginning with a dot? It depends on how you indent your code, but if you use a tab you could use the same command and just replace the 2 spaces with a tab \t like this: :s/\v(\(\w{-})@<!\./\r\t\./g – user9433424 May 11 '16 at 10:50
  • I'm using == to indent. A gist with a proper code. gist.github.com/anonymous/7735cf710289815c85ec719abad8c403 I've try few things for here without success stackoverflow.com/questions/2732618/… – Mio May 11 '16 at 11:03
  • Thank you for the gist, I understand now, I'm going to look for a solution and update the answer if I find something. – user9433424 May 11 '16 at 11:12
  • The last version is faster but their is a problem with indent. All the block is not properly indent. Also I've try to map to a leader key map <Leader>a :s/^ // | s/\v\ze%(\(\w+%(\.\w+)*)@<!\./\="\n " . matchstr(getline('.'), '^\s*')/g<CR> but I get trailing characters errors. :/ – Mio May 12 '16 at 7:42
2

A greedy match should be enough to do the job.

command! SplitDot let _s=@/ <bar> s/\v\.\w+%(\([^)]+\)|\{[^}]+})*/\r\0/g <bar> let @/=_s <bar> keepjumps normal! ``=']']
nnoremap <Leader>sd :SplitDot<CR>

The command works on the original example and this more complicated one:

class MyMethods
  def mymethod
    MyModel.first_method.second(arg).third_method(arg.method).select{|a| a.valid?}.join
  end
end

After using the mapping the output is below and the cursor is at ^:

class MyMethods
  def mymethod
    MyModel
      .first_method
      .second(arg)
      .third_method(arg.method)
      .select{|a| a.valid?}
      .join
      ^
  end
end

The pattern is longer, but easier to extend to more cases like {} blocks.

annotated regex and re-indent binding

I prefer using the = method so Vim can indent for me using the indent rules already established by filetype.

Hope this helps and thanks for the idea. I've already used it in my own work flow.

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.