personal web log written by izabeera and dryobates

vim

Sorting paragraphs in Vim

by dryobates

You use :sort for sorting lines in Vim, right?. How would you sort paragraphs?

I have some list of paragraphs in file that changes quite often. I would like to sort them by pressing one key.

What I mean as paragraphs? My definition of a paragraph is simpler then that in vim’s help. It’s a block of text separated by at least one blank line.

So I have started with some test one-liner:

%s:\(\n\n\_^\|\%^\):\rBEGIN:g | %s:\n:NL:g | s:NLBEGIN:: | %s:BEGIN:\r:g | sort | %s:NL:\r:g

That one-liner sorts whole file of paragraphs. In short it:

  1. replaces all blank lines and beginning of file with BEGIN
  2. changes new lines to NL
  3. changes BEGIN into newlines (at this point every line has compressed contents of whole paragraph)
  4. sort lines
  5. restores NL to their original newlines

It was good for the whole file but failed when I want to sort only a selection of lines.

I have start digging and wrote a function that works on selections:

function! SortParagraphs() range
    execute a:firstline . "," . a:lastline . 'd'
    let @@=join(sort(split(substitute(@@, "\n*$", "", ""), "\n\n")), "\n\n")
    put!
endfunction

vnoremap <F4> :call SortParagraphs()<CR>

It doesn’t look very beautiful (I wouldn’t allow myself for such ugly code in python ;) ) but it works. If we split it into parts it became really simple. Let start from inner part:

substitute(@@, "\n*$", "", "")

Above code strips last end of lines from string in unnamed register. In register all lines are kept as single line.

split(substitute(@@, "\n*$", "", ""), "\n\n")

That splits our string by blank lines. We get list of paragraphs.

sort(split(substitute(@@, "\n*$", "", ""), "\n\n"))

As name suggests - it sorts list of paragraphs.

join(sort(split(substitute(@@, "\n*$", "", ""), "\n\n")), "\n\n")

In the end we join that paragraphs again into one string. So basically we split, sort and join again.

Up to that point we have operate on data in register. Now we have to get data into that register and then put it back.

function! SortParagraphs() range
    execute a:firstline . "," . a:lastline . 'd'
    let @@=join(sort(split(substitute(@@, "\n*$", "", ""), "\n\n")), "\n\n")
    put!
endfunction

That function is declared with “range” keyword. It means that function have to be called with some range and in variables “a:firstline” and “a:lastline” we’ll get range lines. It allows us to delete those lines from buffer into registers:

execute a:firstline . "," . a:lastline . 'd'

Then replace contents in register:

let @@=join(sort(split(substitute(@@, "\n*$", "", ""), "\n\n")), "\n\n")

And put that data again into buffer:

put!

Last part is easy. I have mapped call to function to F4 in visual mode:

vnoremap <F4> :call SortParagraphs()<CR>

Thanks to that if I select lines with V and then press F4 they’ll be sorted.

dryobates
dryobates
Jakub Stolarski. Software engineer. I work professionally as programmer since 2005. Speeding up software development with Test Driven Development, task automation and optimization for performance are things that focus my mind from my early career up to now. If you ask me for my religion: Python, Vim and FreeBSD are my trinity ;) Email: jakub@stolarscy.com

Archive

Tag cloud