3

Generally you can select a visual block for the first n characters of a range of lines, and just press c, insert characters, and press Esc to have it apply to all.

However, this method is rarely possible for all the latter characters, because lines of code are likely to vary in length.

How can I easily change the last three characters of a range of lines?

10

It would not work all the time, but maybe you could temporarily right-align the right border of the code.

Suppose you have the following code containing 3 lines, each with the same level of indentation (8):

        here is foo
        here will be foo
        here was foo

And you want to change foo with bar. You could use the following commands:

  • vip = select the current paragraph
  • :right = right-align it
  • ff = move the cursor on the f character
  • C-v G = go into visual block mode and select the column of f characters
  • C = cut everything after the column
  • bar Esc = insert bar and escape to go back in normal mode
  • gv = visually select the last changed text (the bar column)
  • :left 8 = left-align the paragraph, giving it back its original level of indentation 8

It would look like this: enter image description here

If however, the lines in the block have different levels of indentation, then I don't think this solution can work, as you would probably have to remember the original level of each line, then fix it afterwards.

  • Now this is a creative solution. I actually would like to hotkey this, so the orientation is toggled on a key press. – Akiva Jun 22 '16 at 15:43
  • 2
    @Akiva To toggle the alignment, you would have to define a test to check whether the visually selected lines are left or right-aligned. Maybe you could compare the level of indentation of the first visually selected line with an arbitrary big number: xno <expr> ,x indent("'<") < 40 ? ':right<CR>' : ':left<CR>'. With this mapping, if you hit ,x on a visual selection while the first line has a level of indentation lower than 40, it should right-align it, otherwise left-align. But then your text will lose its original level of indentation. – user9433424 Jun 22 '16 at 16:00
  • 1
    @Akiva For something a little more robust, you could try this: vpaste.net/0xAhN This other mapping should let you choose a level of indentation, when you realign your visual selection to the left. – user9433424 Jun 22 '16 at 16:00
  • 1
    @Akiva I'm not sure this is what you want, but you could right-align the whole buffer by passing the range % to the :right command: :%ri. (:ri is the short form of :right). For the mapping, the previous one was still not very good because it shouldn't ask you what was the original level of indentation. It should be able to remember it itself. This one should do it: vpaste.net/AVw7b But it probably needs to be improved in other ways I didn't think of. – user9433424 Jun 22 '16 at 16:15
  • 1
    @Akiva Ah yes I didn't think about that. But then we're not in visual mode anymore. But yes, I think it's a good idea. You would type: :%ri to right-align. Do your changes. Type: :%le to left-align. Then, gg=G or :norm! gg=G to auto-indent the whole buffer. For visual mode, you would have to adapt the range. In particular the last command would become: :norm! '<='> – user9433424 Jun 22 '16 at 17:38
12

Building on @statox's answer,

:'<,'>s/\v.{3}$/foo/
  • \v very magic option, see :h \v for more info
  • .{3}$ last 3 characters of line
  • foo desired replacement string
7

EDIT Here is a better solution than the one I gave previously:

'<,'>g/.*/norm! $4hCfoo
  • '<,'> apply the command to the visual selection
  • g/.*/ apply the global command on all the lines (of the visual selection)
  • norm! interpret the following string as a normal command
  • $4hCfoo go 4 characters before the end of the line and replace them by foo

EDIT 2 And another one for the fun:

'<,'>g/.*/execute "s/\\%" . (len(getline('.'))-4) . "c.*$/toto"

I think it is overcomplicated but is interesting to understand the behavior of the global command:

  • '<,'> Apply on the visual selection
  • g/.*/ Apply the global command on all the lines of the visual selection
  • execute "..." Execute the following string as a command (We use it to insert a calculation)
  • s/ Aplly a substitution on the following pattern
  • \\%" Beginning of the pattern of the form \%Xc.*$ i.e. from the Xth column to the end of the line
  • . (len(getline('.'))-4) . Calculate the number of the column based on the size of the line minus the number of column to change
  • "c.*$ end of the pattern
  • "toto" The string to use as replacement

The interesting part is that to be able to use . (len(getline('.'))-4) . in the substitution you have to use the global command:

The global command will execute the command on each selected lines one after one, so getline('.') is updated on each line. If you only used

execute "'<,'>s/\\%" . (len(getline('.'))-4) . "c.*$/toto"

The getline('.') would only be the current line and thus you would get unexpected results.


There are probably some better solutions but you could do it using a macro. For example to replace the last 4 characters by foo:

qa$4hCfoo<Esc>jq
  • qa record a macro
  • $4h go 4 character before the end of the line
  • C remove the end of the line and enter insert mode
  • foo the text to insert
  • <Esc> leave insert mode
  • j go to the next line
  • q stop recording the macro

You can then go to the first line and use 5@a to apply the macro on the 5 next lines.

  • 4
    wouldn't '<,'>s/\v.{4}$/foo/ be even simpler? – Sundeep Jun 22 '16 at 11:45
  • 2
    @spasic Ahah I've never been good at vim golf. Indeed it is a way better solution, you should make it an answer – statox Jun 22 '16 at 11:49

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.