0

Given an input file of the form

#1. This STUFF
Content related to 1
...and some tips 

#4. Fouth heading 
4para
4para


#3. 
3para
3para

I'd like to be able to sort it to get:

#1. This STUFF
Content related to 1
...and some tips 

#3. 
3para
3para

#4. Fouth heading 
4para
4para

specifically I'd like to be able to do this with a command that looks like:

 :sort break="#" 

i.e. change the linebreak character to "#" for the duration of the sort.

Is this possible in any way? A similar question might be "Can I sort the fuctions in a python file alphabetically by name?"

1
  • 2
    I think it would be easier to do with a tool like awk rather than Vim. Awk might not be the easiest tool to master it for this kind of manipulations it is very efficient.
    – statox
    Jun 26, 2019 at 13:35

2 Answers 2

2
for id in reverse(range(1,100)) | sil exe printf('g/\v^#%d\./,/\v.*(\n#\d+\.|%%$)/ m0', id) | endfor

Loop from 100 to 1, execute :h :global for each number, use :h :m to move each block to file start. This method is slow, see update for a better way.

Break down of g/\v^#3\./,/\v.*(\n#\d+\.|%$)/ m0:

  • /\v^#3+\./ find line starts with #3..
  • ,/\v.*(\n#\d+\.|%$)/ this a :h [range] , it starts from current line, ends at line followed by line starts with #\d+\. or end of file.
  • m0 move to file start.

update

A more efficient method:

%d | put ='#'.join(sort(split(@@, '\v#\ze\d+\.'),'N'),'#') | 1d
  • %d delete whole file into default register
  • put = :h :put text from register :h quote= after current line (line 1).
  • '#'.join(sort(split(@@, '\v#\ze\d+\.'),'N'),'#') split by #, sort numberical, join by #, add leading # for 1st item.
  • 1d remove 1st line.
2
  • I learned quite a lot reading this answer.
    – Joe
    Jun 27, 2019 at 10:04
  • Hopefully "get in the habit of using global in a loop" is not one of your learnings. ;) In practice, even though we're looking at O(n^2) efficiency, it might perform just fine for reasonable value of n. If not, I'd go with statox's advice and use awk or equivalent.
    – B Layer
    Jun 27, 2019 at 22:15
1

I would use a special marker for end-of-line, join all lines in a section into one line, sort the lines, and replace the markers by linefeeds:

" Add an empty line at the end of file so the search pattern
" in the :global command will work.
:$put =''
" Mark every end-of-line.
:%s/$/<EOL>/
" Join all lines in a section starting with a hash up until
" the line before the next section or the end of the file.
:g/^#/;/\(^#\|\%$\)/-1join!
" Get rid of the additional empty line.
:$d
" Sort the file by the numbers after the hash signs.
:sort n /^#/
" Re-insert the linefeeds.
:%s/<EOL>/\r/g

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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