I am taking notes in an outline format that looks like this:

* Micro topic 1
** Microbes are small
** You can't see them!
*** Isn't that something?
* Micro topic 2
** I hope I like the teacher
*** She will be great!
** Micro is cool!

where one star is a top level heading, and more stars indicate subheadings as in a typical bullet point outline.

My goal is to automatically convert them into a .csv file like so:

"* Micro topic 1"," "** Microbes are small", "** You can't see them!", "*** Isn't that something?",
"* Micro topic 2", "** I hope I like the teacher", "*** She will be great!", "** Micro is cool!"

Where the first column contains only top level headings (lines beginning with one star), and the following columns are subheadings belonging to their parent top level heading. My challenge is that I don't know how to get vim macros to be aware of when the encounter a new top level heading, and should therefore not join it to the previous line.

So far I've got:


to surround a line in quotes, add a comma, join with the following line and repeat. But how to skip joining lines if the next line starts with *, or regular expression ^\*\s ?


Using :global, I would do this in two parts:

First, convert all the lines to strings:


Second, replace newlines on all the lines except the last before each top level heading with commas:


The pattern for global /"\*\{2,}/ matches all the lines where quote is followed by two or more stars. The command executed for each of these lines is :-1s/\n/,, i.e. for the line before (-1 as range), substitute newline with a comma.

  • It appears to have worked beautifully - thank you!
    – truthling
    Jan 19 '16 at 5:35
  • I realized I need a slightly different solution. I need all of the subheadings to be in one column and separated by <br>, instead of in their own separate columns. E.g. > "* Micro topic 1", "** Microbes are small<br>** You can't see them!<br>*** Isn't that something?" Should I post a new question or edit this question?
    – truthling
    Jan 29 '16 at 6:03

You can find the line before the next top level heading with the regexp \n\*[^*]:

  • \n newline
  • \* followed by a star
  • [^*] not followed by another star (brackets are only for grouping)

This fails at the end of the file. We can use \%$ to find the end of the file in that case, together with the \| OR operator for vim regeps.

Also, you can visually select lines and press J to join them. Also, reselecting a visual selection is done via gv. So this works out the following way

  1. Start at a top level heading line
  2. <Shift>V to start linewise visual mode
  3. /\n\*[^*]\|\%$ to go to the line before the next top level heading, or if that fails, to the last line of the file
  4. :s/^/"/ to insert at the beginning of each line (the range before s is inserted automatically for you)
  5. gv to reselect
  6. :s/$/",/ to insert at the end of each line (again, range inserted automatically
  7. gv to reselect
  8. J to join all the lines
  9. :s/,$// to delete the last comma (no range neccessary since we operate on the current line)
  10. j to go to the next line, a top level heading (fails at the end of the file, which is good)

Put that into a macro, and it will do what you want, as I understand it. Of course, steps 4 to 7 could be done globally prior to the macro, but that way it is suitable for a custom command.

  • It works until reaching the last top level heading at which point it surrounds the last top level heading with quotes, doesn't add the comma, and joins it with the previous line, leaving the last set of subheadings intact. So I get: ""* topic 1", "** subheading", "** another", "*** another"", "* topic 2" ** subheading *** subsubheading *** subsubheading 2 ** another subheading In /\n\*[^*] \| \%$ should there be spaces on either side of \|? It looks like the pattern only matches with no space to the left of \| and a space to the right of it.
    – truthling
    Jan 19 '16 at 5:26
  • You're right, there should be no spaces in the search pattern, I fixed it. Jan 19 '16 at 11:48

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.