2

I have a rather long chat transcript that I copied out of a window, and when I pasted it into vim, it put two line breaks after every username, and then what the user typing said. I'd like to:

  1. Search for the > character
  2. Go to the top of the file.
  3. Start recording the macro
  4. Move to the next match.
  5. Press the delete key twice to move to move the text up to the same line as the user's name
  6. Finish recording the macro.
  7. Run the previously recorded macro for every match in the file.

How would one go about this in VIM?

4

As steve pointed out, :%s/>\n\n/> / is useful here (although the g flag he used is superfluous.)

I want to mention another command that is applicable here: the "join" command, which is J.

So for a macro you could use:

ggqq/>$EnterJJq999@q

Explanation:

gg                  # Move to start of file
  qq                # Start recording macro into register q
    />$^M           # Search for the ">" character at the end of a line
         JJ         # Join the next two lines onto the current line
           q        # Stop recording macro
            999@q   # Play macro back 999 times.
                    # (It will stop when ">" is no longer found at the end of any line.)
  • Ah, I thought without g it would stop after first match. Thanks for pointing this out – Steve Dec 22 '15 at 15:27
7

One way of deleting two new lines after every > is as follows

:%s/>\n\n/> /

To explain, this is a global search and replace (:%s) finding > followed by two newline characters (>\n\n) replacing with >.

Or for recording the macro you asked for

  1. Search for the > character
/>
  1. Go to the top of the file.
gg
  1. Start recording the macro
qa
  1. Move to the next match.
n
  1. Press the delete key twice to move to move the text up to the same line as the user's name
A<del><del><esc>
  1. Finish recording the macro.
q
  1. Run the previously recorded macro for every match in the file.
@@

This last line would run it once. Could run it a number of times, say 5, with 5@@. To run the macro until the end of the file (from saginaw in comments), you would need to :set nowrapscan (to stop at the end of the file when searching with n), define a as a recursive macro with

qA@aq

then :set wrapscan to return to the default search behaviour.

  • I'm not sure this does what you're asking. Maybe you could give a few lines of an example input and output? – Steve Dec 18 '15 at 15:04
  • Thank you for posting, but no, this would still result in the chat user and the text they typed being on separate lines. – leeand00 Dec 18 '15 at 15:06
  • @leeand00 Think I understand. See the new oneliner at the start of this question – Steve Dec 18 '15 at 15:15
  • @Steve If you want the macro to run until it hits the end of the file, you can make it recursive. To make a macro recursive, first you have to clear the register in which you're going to store it. If it's a, then hit qaq. Then, begin the recording, and type whatever is needed. Just before ending the recording, hit @a so that the macro calls itself. If you have an already non-recursive macro in register a and want to make it recursive, there are 2 ways. From normal mode qA@aq, or from command line mode: :let @a .= '@a' – saginaw Dec 18 '15 at 15:49
  • 1
    @Steve Yes you're right, in this particular case you would probably have to prevent vim from searching back from the beginning of the file after it hits the end, otherwise the macro will never end. To do so, go to the beginning of the file and type set nowrapscan. It should tell vim to stop at the end of the file. You can reenable the option afterwards if you want set wrapscan. – saginaw Dec 18 '15 at 16:06
2

Macros are good for quick-and-dirty solutions in the moment, but command-line commands are more efficient (though this is only normally noticeable on really large files). One possible method:

:g/>$/j3

From :h :g:

:[range]g[lobal]/{pattern}/[cmd]
        Execute the Ex command [cmd] (default ":p") on the
        lines within [range] where {pattern} matches.

The [cmd] in this case is :j. From :h :j:

:[range]j[oin][!] {count} [flags]
        Join {count} lines, starting with [range] (default:
        current line |cmdline-ranges|).  Same as "J", except
        with [!] the join does not insert or delete any
        spaces.

In order to join the current line with the next two lines, you actually have to use a {count} of 3, as I've put here.

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.